/* global document */
/* eslint-disable no-undef, no-console, no-alert, no-unused-vars */

import Alpine from 'alpinejs';
import collapse from '@alpinejs/collapse';
import persist from '@alpinejs/persist';
import focus from '@alpinejs/focus';
import sort from '@alpinejs/sort';
import tippy from 'tippy.js';
import ApexCharts from 'apexcharts';
import { defineComponents } from '../shared/web-components/index.js';
import { registerRewardsPanelComponents } from '../shared/alpine-components/rewards-panel.js';
import { registerSetupTasks } from './components/setup-tasks.js';
import { registerMergeTagsDropdown } from './components/merge-tags-dropdown.js';
import { registerMediaUpload } from './components/media-upload.js';
import { registerDrawerSections, applyCurrencySymbol } from './components/drawer-sections.js';
import { registerDeepLinkHandler } from './components/deep-link-handler.js';
import { initColorPicker } from './components/color-picker.js';

// Register Alpine plugins
Alpine.plugin(collapse);
Alpine.plugin(persist);
Alpine.plugin(focus);
Alpine.plugin(sort);

// Make libraries available globally since they're used directly in Alpine components
window.Alpine = Alpine;
window.tippy = tippy;
window.ApexCharts = ApexCharts;

document.addEventListener('alpine:init', () => {
	const notificationsStore = {
		drawerOpen: false,
		page: 1,
		perPage: 5,
		widgetCount: 5,
		notifications: [],
		translations: {
			newNotifications: 'New Notifications',
			noNotifications: 'No new notifications',
			dismissAll: 'Dismiss All',
			viewNotifications: 'View Notifications',
			moreNotificationsSingular: 'You have %d more notification',
			moreNotificationsPlural: 'You have %d more notifications',
		},
		scrollListener: null,
		scrollBaseline: 0,
		ajaxNonce: null,
		ajaxUrl: null,
		load(config = {}, forceReload = false) {
			const parsed = this.parseConfig(config);
			this.perPage = this.normalizeNumber(parsed.perPage, this.perPage);
			this.widgetCount = this.normalizeNumber(parsed.widgetPreview, this.widgetCount);
			if (parsed.translations && typeof parsed.translations === 'object') {
				this.translations = { ...this.translations, ...parsed.translations };
			}
			const notifications = Array.isArray(parsed.notifications) ? parsed.notifications : [];
			this.notifications = notifications.map((item, index) =>
				this.prepareNotification(item, index)
			);
			this.page = 1;
			this.ensurePageInRange();
		},
		parseConfig(config) {
			if (typeof config === 'string' && config.trim() !== '') {
				try {
					return JSON.parse(config);
				} catch (error) {
					console.error('RewardsWP notifications: failed to parse config', error);
					return {};
				}
			}
			if (typeof config === 'object' && config !== null) {
				return config;
			}
			return {};
		},
		normalizeNumber(value, fallback) {
			const numeric = Number(value);
			return Number.isFinite(numeric) && numeric > 0 ? numeric : fallback;
		},
		prepareNotification(item, index) {
			const source = typeof item === 'object' && item !== null ? item : {};
			const notification = {
				id: source.id || `notification-${index + 1}`,
				title: source.title || '',
				message: source.message || '',
				date: source.date || '',
				relative_date: source.relative_date || '',
				learn_more_url: source.learn_more_url || '#',
				dismiss_url: source.dismiss_url || '#',
				dismissed: !!source.dismissed,
				notification_type: source.notification_type || 'success',
			};

			// Include button data if present
			if (source.btns && typeof source.btns === 'object') {
				notification.btns = source.btns;
			}

			return notification;
		},
		get activeNotifications() {
			return this.notifications.filter((notification) => !notification.dismissed);
		},
		get hasNotifications() {
			return this.activeNotifications.length > 0;
		},
		get totalPages() {
			return this.hasNotifications
				? Math.ceil(this.activeNotifications.length / this.perPage)
				: 0;
		},
		get paginationNumbers() {
			return Array.from({ length: this.totalPages }, (_, index) => index + 1);
		},
		get paginatedNotifications() {
			// Return all notifications (including dismissed) so animations can run
			// The x-show directive will handle visibility
			const start = (this.page - 1) * this.perPage;
			return this.notifications.slice(start, start + this.perPage);
		},
		get widgetNotifications() {
			// Return all notifications (including dismissed) so animations can run
			// The x-show directive will handle visibility
			return this.notifications.slice(0, this.widgetCount);
		},
		get extraNotificationsCount() {
			const extra = this.activeNotifications.length - this.widgetCount;
			return extra > 0 ? extra : 0;
		},
		moreNotificationsLabel() {
			const arrow = ' →';
			const extra = this.extraNotificationsCount;
			const baseLabel = this.translations.viewNotifications || 'View Notifications';
			if (extra <= 0) {
				return `${baseLabel}${arrow}`;
			}
			const singularTemplate =
				this.translations.moreNotificationsSingular || 'You have %d more notification';
			const pluralTemplate =
				this.translations.moreNotificationsPlural || 'You have %d more notifications';
			const template = extra === 1 ? singularTemplate : pluralTemplate;
			return `${template.replace('%d', extra)}${arrow}`;
		},
		titleText() {
			return `(${this.activeNotifications.length}) ${this.translations.newNotifications}`;
		},
		open() {
			console.debug('RewardsWP notifications drawer: open()');
			this.page = 1;
			this.drawerOpen = true;
			this.scrollBaseline = window.scrollY || 0;
			this.ensurePageInRange();
			this.attachScrollListener();
		},
		close() {
			console.debug('RewardsWP notifications drawer: close()');
			this.drawerOpen = false;
			this.detachScrollListener();
		},
		goTo(pageNumber) {
			this.page = pageNumber;
			this.ensurePageInRange();
		},
		async dismissNotification(id) {
			const target = this.notifications.find((notification) => notification.id === id);
			if (!target) {
				return;
			}

			// Optimistic UI update
			target.dismissed = true;

			// Persist to database via AJAX
			if (this.ajaxUrl && this.ajaxNonce) {
				try {
					// Ensure ID is always a string for consistency
					const notificationId = String(id);

					const formData = new FormData();
					formData.append('action', 'rewardswp_dismiss_notification');
					formData.append('nonce', this.ajaxNonce);
					formData.append('notification_id', notificationId);

					const response = await fetch(this.ajaxUrl, {
						method: 'POST',
						body: formData,
					});

					const data = await response.json();

					if (!data.success) {
						console.error(
							'RewardsWP: Failed to dismiss notification:',
							data.data?.message || 'Unknown error'
						);
						// Revert UI on failure
						target.dismissed = false;
					}
				} catch (error) {
					console.error('RewardsWP: Error dismissing notification:', error);
					// Revert UI on error
					target.dismissed = false;
				}
			}

			// Clean up after animation completes
			setTimeout(() => {
				this.ensurePageInRange();
			}, 350); // Slightly longer than animation duration (300ms)
		},
		async dismissAll() {
			// Add dismissOrder for staggered animation
			let activeCount = 0;
			const dismissedIds = [];

			this.notifications.forEach((notification) => {
				if (!notification.dismissed) {
					notification.dismissOrder = activeCount++;
					notification.dismissed = true;
					dismissedIds.push(notification.id);
				}
			});

			// Persist to database via AJAX
			if (this.ajaxUrl && this.ajaxNonce && dismissedIds.length > 0) {
				try {
					const formData = new FormData();
					formData.append('action', 'rewardswp_dismiss_all_notifications');
					formData.append('nonce', this.ajaxNonce);

					const response = await fetch(this.ajaxUrl, {
						method: 'POST',
						body: formData,
					});

					const data = await response.json();

					if (!data.success) {
						console.error(
							'RewardsWP: Failed to dismiss all notifications:',
							data.data?.message || 'Unknown error'
						);
						// Revert UI on failure
						dismissedIds.forEach((id) => {
							const notification = this.notifications.find((n) => n.id === id);
							if (notification) {
								notification.dismissed = false;
								delete notification.dismissOrder;
							}
						});
					}
				} catch (error) {
					console.error('RewardsWP: Error dismissing all notifications:', error);
					// Revert UI on error
					dismissedIds.forEach((id) => {
						const notification = this.notifications.find((n) => n.id === id);
						if (notification) {
							notification.dismissed = false;
							delete notification.dismissOrder;
						}
					});
				}
			}

			// Clean up after all animations complete
			setTimeout(
				() => {
					this.notifications.forEach((n) => delete n.dismissOrder);
					this.ensurePageInRange();
				},
				activeCount * 50 + 350
			); // Stagger time + animation duration (300ms)
		},
		attachScrollListener() {
			if (this.scrollListener) {
				return;
			}
			this.scrollListener = () => {
				if (!this.drawerOpen) {
					return;
				}
				const current = window.scrollY || 0;
				if (Math.abs(current - this.scrollBaseline) > 16) {
					this.close();
				}
			};
			window.addEventListener('scroll', this.scrollListener, { passive: true });
		},
		detachScrollListener() {
			if (!this.scrollListener) {
				return;
			}
			window.removeEventListener('scroll', this.scrollListener);
			this.scrollListener = null;
			this.scrollBaseline = 0;
		},
		ensurePageInRange() {
			if (!this.hasNotifications) {
				this.page = 1;
				return;
			}
			if (this.page > this.totalPages) {
				this.page = this.totalPages;
			}
			if (this.page < 1) {
				this.page = 1;
			}
		},
	};

	Alpine.store('notificationsDrawer', notificationsStore);
	window.rewardswpNotifications = Alpine.store('notificationsDrawer');

	window.addEventListener('rewardswp:open-notifications', () => {
		notificationsStore.open();
	});

	// Drawer Store
	const drawerStore = {
		isOpen: false,
		type: null,
		view: 'list',
		actionId: null,
		itemId: null,
		title: '',
		subtitle: '',
		addTitle: '',
		isLoading: false,
		isNew: true,
		isUpgrade: false,
		errors: {},
		_showProductError: false,
		selectedAction: {},
		originalSnapshot: null,
		resetTimeoutId: null, // Track the timeout ID so we can cancel it

		open(config = {}) {
			// This prevents the old timeout from clearing data after we've opened a new drawer
			if (this.resetTimeoutId !== null) {
				clearTimeout(this.resetTimeoutId);
				this.resetTimeoutId = null;
			}

			this.type = config.type || null;

			this.view = config.view || 'list';

			this.actionId = config.actionId || null;

			this.itemId = config.itemId || null;

			this.title = config.title || '';

			this.subtitle = config.subtitle || '';

			this.addTitle = config.addTitle || config.title || '';

			this.selectedAction = config.selectedAction || {};

			this.originalSnapshot = config.originalSnapshot || null;

			this.isNew = config.isNew !== undefined ? config.isNew : true;

			this.isUpgrade = config.isUpgrade || false;

			this.errors = {};
			this._showProductError = false;

			this.isOpen = true;
		},

		backToList() {
			this.view = 'list';
			this.selectedAction = {};
			this.originalSnapshot = null;
			this.isUpgrade = false;

			const earnData = window.rewardswpDrawerData?.earn;
			if (this.type === 'earn' && earnData) {
				this.title = earnData.addTitle || 'Add Action';
				this.subtitle =
					earnData.addSubtitle || 'Choose an action for customers to earn points';
			}

			const spendData = window.rewardswpDrawerData?.spend;
			if (this.type === 'spend' && spendData) {
				this.title = spendData.addTitle || 'Add Reward';
				this.subtitle = spendData.addSubtitle || 'Choose a reward type';
			}

			const referralsData = window.rewardswpDrawerData?.referrals;
			if (this.type === 'referral' && referralsData) {
				// actionId is 'advocate' or 'friend'
				if (this.actionId === 'advocate') {
					this.title = referralsData.advocateAddTitle || 'Add Advocate Reward';
				} else if (this.actionId === 'friend') {
					this.title = referralsData.friendAddTitle || 'Add Friend Reward';
				}
				this.subtitle = referralsData.addSubtitle || 'Choose a reward type';
			}
		},

		close() {
			if (this.resetTimeoutId !== null) {
				clearTimeout(this.resetTimeoutId);
				this.resetTimeoutId = null;
			}
			this.isOpen = false;
			this.isLoading = false;
			this.errors = {};
			this._showProductError = false;

			this.resetTimeoutId = setTimeout(() => {
				this.type = null;
				this.actionId = null;
				this.itemId = null;
				this.title = '';
				this.subtitle = '';
				this.resetTimeoutId = null;
			}, 300);
		},

		save() {
			this.isLoading = true;

			if (this.type === 'earn') {
				if (this.isNew) {
					window.dispatchEvent(new CustomEvent('add-earn-action'));
				} else {
					window.dispatchEvent(new CustomEvent('update-earn-action'));
				}
				return;
			}

			if (this.type === 'spend') {
				if (this.isNew) {
					window.dispatchEvent(new CustomEvent('add-spend-action'));
				} else {
					window.dispatchEvent(new CustomEvent('update-spend-action'));
				}
				return;
			}

			if (this.type === 'referral') {
				if (this.isNew) {
					window.dispatchEvent(new CustomEvent('save-referral-reward'));
				} else {
					window.dispatchEvent(new CustomEvent('update-referral-reward'));
				}
				return;
			}

			window.dispatchEvent(
				new CustomEvent('rewardswp:save-settings', {
					detail: {
						onComplete: () => {
							this.isLoading = false;
							this.close();
						},
					},
				})
			);
		},

	hasChanges() {
		// Check if this is a managed drawer type (earn, spend, or referral)
		const isManagedType =
			this.type === 'earn' ||
			this.type === 'spend' ||
			(this.type && this.type.startsWith('referral'));

		if (!isManagedType) {
			return true;
		}

		// For new items, Done button should be enabled if selectedAction has data
		if (this.isNew) {
			return Object.keys(this.selectedAction || {}).length > 0;
		}

		// For existing items, disable Done button if no data or snapshot exists
		if (Object.keys(this.selectedAction || {}).length === 0 || !this.originalSnapshot) {
			return false;
		}

		// Compare current state to original snapshot
		try {
			return (
				JSON.stringify(this.selectedAction) !==
				JSON.stringify(this.originalSnapshot)
			);
		} catch (err) {
			return true;
		}
	},
	};

	Alpine.store('drawer', drawerStore);
	window.rewardswpDrawer = Alpine.store('drawer');

	// Global Config Store
	Alpine.store('config', {
		planTier: 'lite', // 'lite', 'basic', 'plus', or 'pro'
		currencySymbol: '$',
		hasProFeatures: false,
		hasRewardExpiration: false,
		hasReferralRewardExpiration: false,
		accessibleFeatures: [],

		setup(data) {
			this.planTier = data.planTier || 'lite';
			this.currencySymbol = data.currencySymbol || '$';
			this.hasProFeatures = data.hasProFeatures || false;
			this.hasRewardExpiration = data.hasRewardExpiration || false;
			this.hasReferralRewardExpiration = data.hasReferralRewardExpiration || false;
			this.accessibleFeatures = data.accessibleFeatures || [];
		},

		isLite() {
			return this.planTier === 'lite';
		},

		isPro() {
			return this.planTier === 'pro';
		},

		canAccess(feature) {
			return this.accessibleFeatures.includes(feature);
		},
	});
});

// Helper functions for spend actions management
window.rewardsWPSpendActions = {
	findActionIndex: function (actions, actionId) {
		if (!Array.isArray(actions)) {
			return -1;
		}
		for (var i = 0; i < actions.length; i++) {
			if (actions[i].id === actionId) {
				return i;
			}
		}
		return -1;
	},

	findAction: function (actions, actionId) {
		var index = this.findActionIndex(actions, actionId);
		return index !== -1 ? actions[index] : null;
	},

	toggleAction: function (actions, actionId, enabled) {
		var index = this.findActionIndex(actions, actionId);
		if (index !== -1) {
			actions[index].enabled = !!enabled;
		}
		return actions;
	},

	removeAction: function (actions, actionId) {
		if (!Array.isArray(actions)) {
			return actions;
		}
		return actions.filter(function (action) {
			return action.id !== actionId;
		});
	},

	isValidAction: function (key, action) {
		if (Array.isArray(action)) {
			return true;
		}

		if (!action) {
			return false;
		}

		if (key === 'fixed_discount' && action.points_per_dollar) {
			return true;
		}

		if (key === 'percentage_discount' && action.points_per_percent) {
			return true;
		}

		if ((key === 'free_shipping' || key === 'free_product') && action.points_required) {
			return true;
		}

		return false;
	},

	convertToNewFormat: function (key, action, defaults) {
		if (Array.isArray(action)) {
			return action;
		}

		return {
			id: key + '_' + Date.now(),
			type: key,
			enabled: typeof action.enabled !== 'undefined' ? Boolean(Number(action.enabled)) : true,
			title: action.title || (defaults && defaults[key] ? defaults[key].title : '') || '',
			description:
				action.description ||
				(defaults && defaults[key] ? defaults[key].description : '') ||
				'',
			points_per_dollar: action.points_per_dollar,
			points_per_percent: action.points_per_percent,
			points_required: action.points_required,
		};
	},

	convertSavedActionsToArray: function (savedActions, defaults) {
		if (!savedActions) {
			return [];
		}

		if (Array.isArray(savedActions)) {
			return savedActions;
		}

		var validActions = [];
		var self = this;

		for (var key in savedActions) {
			if (savedActions.hasOwnProperty(key)) {
				var action = savedActions[key];
				if (self.isValidAction(key, action)) {
					validActions.push(self.convertToNewFormat(key, action, defaults));
				}
			}
		}

		return validActions;
	},
};

function rewardsWPAddUpgradeBarClass() {
	document.body.classList.add('rewardswp-has-bar');
}

document.addEventListener('DOMContentLoaded', function () {
	rewardsWPAddUpgradeBarClass();
});

function initStickyHeader(sentinelId, headerId, additionalClasses = []) {
	const sentinel = document.getElementById(sentinelId);
	const header = document.getElementById(headerId);
	const observer = new IntersectionObserver(
		([e]) => {
			if (e.intersectionRatio < 1) {
				header.classList.add('shadow-lg', ...additionalClasses);
				header.classList.remove('border-b');
			} else {
				header.classList.remove('shadow-lg', ...additionalClasses);
				if (additionalClasses.length) {
					header.classList.add('border-b');
				}
			}
		},
		{ threshold: [1] }
	);

	if (sentinel && header) {
		observer.observe(sentinel);
	}
}

document.addEventListener('alpine:init', () => {
	Alpine.data('choicesComponent', (config) => {
		return {
			query: '',
			results: [],
			selected: [],
			showResults: false,
			debounceTimer: null,
			alpineEl: null,
			optionsKey: '',
			hasErrors: false,
			ref: config.ref || '',
			xModel: config.xModel || '',
			selectedOptions: config.selectedOptions || [],
			minQueryLength: config.minQueryLength || 3,
			options: config.options || [],
			ajaxAction: config.ajaxAction || '',
			nonce: config.nonce || '',
			multiple: config.multiple || false,
			required: config.required || false,
			requiredRef: config.requiredRef || '',
			get selectedIds() {
				return this.selected.map((item) => item.id);
			},
			init() {
				this.alpineEl = this.$refs.hasOwnProperty(this.ref)
					? this.$refs[this.ref]
					: this.$root;

				this.initializeSelected();

				// Watch for changes in the selectedOptions binding to sync the component
				// This is needed when drawer context changes (e.g., switching between Advocate and Friend).
				if (typeof this.selectedOptions === 'string') {
					this.$watch(this.selectedOptions, (newVal) => {
						this.syncSelected(newVal);
					});
				}
			},
			initializeSelected() {
				const initialSelectedIds = Array.isArray(this.selectedOptions)
					? this.selectedOptions
					: Alpine.evaluate(this.alpineEl, this.selectedOptions.toString());

				if (typeof this.options === 'string') {
					this.optionsKey = this.options;
				}

				this.options = Array.isArray(this.options)
					? this.options
					: Alpine.evaluate(this.alpineEl, this.optionsKey);

				if (!this.options) {
					this.options = [];
					// Initialize the options key if it doesn't exist
					if (this.optionsKey) {
						try {
							Alpine.evaluate(this.alpineEl, `${this.optionsKey} = []`);
						} catch (error) {
							console.warn(
								'Could not initialize options key:',
								this.optionsKey,
								error
							);
						}
					}
				}

				if (Array.isArray(initialSelectedIds)) {
					// Handle both array of IDs and array of objects
					this.selected = initialSelectedIds
						.map((item) => {
							if (typeof item === 'object' && item.id) {
								// Already an object with id and name
								return item;
							} else {
								// It's an ID, find the corresponding option
								return this.options.find((option) => option.id === item);
							}
						})
						.filter(Boolean);

					this.results = this.options.filter(
						(option) => !this.selected.some((sel) => sel.id === option.id)
					);
				}
			},
			syncSelected(newSelectedOptions) {
				// Sync the selected array when the parent binding changes.
				if (!Array.isArray(newSelectedOptions)) {
					this.selected = [];
					return;
				}

				this.selected = newSelectedOptions
					.map((item) => {
						if (typeof item === 'object' && item.id) {
							return item;
						}
						return this.options.find((option) => option.id === item);
					})
					.filter(Boolean);

				this.results = this.options.filter(
					(option) => !this.selected.some((sel) => sel.id === option.id)
				);
			},
			validate() {
				if (this.required && this.requiredRef) {
					Alpine.evaluate(this.$root, `${this.requiredRef} = selected.length === 0;`);

					this.hasErrors = 0 === this.selected.length;

					if (this.hasErrors) {
						this.$refs.searchField.focus();
					}
				}
			},
			updateParentModel() {
				if (this.required && this.requiredRef) {
					Alpine.evaluate(this.$root, `${this.requiredRef} = selected.length === 0;`);

					this.hasErrors = 0 === this.selected.length;
				}

				if (!this.xModel) {
					return;
				}

				// Send clean objects with id and name (same format for both spend actions and referrals)
				const dataToSend = this.selected.map((item) => ({
					id: item.id,
					name: item.name,
				}));

				Alpine.evaluate(this.alpineEl, `${this.xModel} = ${JSON.stringify(dataToSend)}`);
			},
			updateOptions(items) {
				this.options = [...this.options, ...items].reduce((acc, item) => {
					if (!acc.some((existingItem) => existingItem.id === item.id)) {
						acc.push(item);
					}
					return acc;
				}, []);

				if (this.optionsKey) {
					try {
						Alpine.evaluate(
							this.alpineEl,
							`${this.optionsKey} = ${JSON.stringify(this.options)}`
						);
					} catch (error) {
						console.warn('Could not update options key:', this.optionsKey, error);
					}
				}
			},
			search() {
				clearTimeout(this.debounceTimer);

				this.debounceTimer = setTimeout(async () => {
					if (this.query.length < this.minQueryLength) {
						this.results = [];
						return;
					}

					if (this.ajaxAction) {
						const formData = new FormData();

						formData.append('action', this.ajaxAction);
						formData.append('_ajax_nonce', this.nonce);
						formData.append('query', this.query);

						try {
							const response = await fetch(ajaxurl, {
								method: 'POST',
								body: formData,
							});

							const result = await response.json();

							this.updateOptions(result.data.items);

							this.results = result.data.items
								.filter(
									(item) =>
										// Match by name OR by ID (for ID search).
										(item.name
											.toLowerCase()
											.includes(this.query.toLowerCase()) ||
											item.id.toString() === this.query.trim()) &&
										!this.selected.some((sel) => sel.id === item.id)
								)
								.map((item) => this.highlightMatch(item));

							if (this.results.length) {
								this.showResults = true;
							}
						} catch (error) {
							console.error('Ajax error:', error);
						}

						return;
					}

					this.results = this.options
						.filter(
							(item) =>
								item.name.toLowerCase().includes(this.query.toLowerCase()) &&
								!this.selected.some((sel) => sel.id === item.id)
						)
						.map((item) => this.highlightMatch(item));

					if (this.results.length) {
						this.showResults = true;
					}
				}, 300);
			},

			highlightMatch(item) {
				const regex = new RegExp(`(${this.query})`, 'gi');
				let highlightedName = item.name.replace(
					regex,
					'<span class="bg-yellow-200 font-bold">$1</span>'
				);

				// Append price if available.
				if (item.price) {
					highlightedName += ` <span class="text-gray-500">(${item.price})</span>`;
				}

				return { ...item, highlightedName };
			},

			select(item) {
				if (this.selected.some((sel) => sel.id === item.id)) {
					return;
				}

				if (this.multiple) {
					this.selected.push(item);
				} else {
					this.selected = [item];
				}

				this.query = '';
				this.results = [];
				this.showResults = false;
				this.results = this.options.filter(
					(option) => !this.selected.some((sel) => sel.id === option.id)
				);
				this.updateParentModel();
			},

			remove(item) {
				this.selected = this.selected.filter((sel) => sel.id !== item.id);

				this.results = this.options.filter(
					(option) => !this.selected.some((sel) => sel.id === option.id)
				);
				this.updateParentModel();

				if (this.required && this.hasErrors) {
					this.$refs.searchField.focus();
				}
			},
		};
	});

	Alpine.data('brandColorPicker', (config) => ({
		color: config.initialColor || '#1f2937',
		brandForegroundColor: '',
		brandForegroundMode: config.initialMode || 'auto',
		defaultColor: config.defaultColor || '#1f2937',
		contrastRatio: 0,
		isLowContrast: false,

		init() {
			this.updateForegroundColor();

			// Position the foreground mode marker and contrast tooltip after DOM is ready.
			// Use IntersectionObserver to handle the case where the component
			// starts hidden (e.g., inside an inactive tab with x-show).
			this.$nextTick(() => {
				this.positionFgMarker();
				this.updateContrastTooltip();

				// If marker has zero width, the element is likely hidden.
				// Wait for it to become visible before positioning.
				if (this.$refs.fgMarker && this.$refs.fgMarker.offsetWidth === 0) {
					const observer = new IntersectionObserver((entries) => {
						if (entries[0].isIntersecting) {
							this.positionFgMarker();
							observer.disconnect();
						}
					});
					observer.observe(this.$el);
				}
			});

			// Watch for color changes and dispatch events
			this.$watch('color', () => {
				this.updateForegroundColor();
				this.dispatchColorUpdates();
				this.updateContrastTooltip();
			});

			// Watch for mode changes
			this.$watch('brandForegroundMode', () => {
				this.updateForegroundColor();
				this.dispatchColorUpdates();
				this.updateContrastTooltip();
				window.dispatchEvent(new CustomEvent('rewardswp:settings-changed'));
			});

		},

		updateForegroundColor() {
			if (this.brandForegroundMode === 'auto') {
				this.brandForegroundColor = this.getContrastingTextColor(this.color);
			} else if (this.brandForegroundMode === 'light') {
				this.brandForegroundColor = '#ffffff';
			} else {
				this.brandForegroundColor = this.deriveDarkForeground(this.color);
			}

			// Calculate and store contrast ratio
			this.contrastRatio = this.calculateContrastRatio(this.brandForegroundColor, this.color);
			this.isLowContrast = this.contrastRatio < 3;
		},

		dispatchColorUpdates() {
			// Dispatch to window so Shadow DOM components can receive it
			window.dispatchEvent(new CustomEvent('setting-updated', {
				detail: { key: 'brand_color', value: this.color }
			}));
			window.dispatchEvent(new CustomEvent('setting-updated', {
				detail: { key: 'brand_foreground_color', value: this.brandForegroundColor }
			}));

			// Notify main component of state change for dirty tracking
			window.dispatchEvent(new CustomEvent('rewardswp:settings-changed'));
		},

		updateColor(newColor) {
			this.color = newColor;
		},

		/**
		 * Update the contrast warning tooltip content, creating it if needed.
		 */
		updateContrastTooltip() {
			const el = this.$refs.contrastWarning;
			if ( ! el ) return;

			const msg = `Low contrast (${ this.getFormattedContrastRatio() }) — text may be hard to read`;
			if ( el._tippy ) {
				el._tippy.setContent( msg );
			} else {
				tippy( el, { content: msg, placement: 'top', theme: 'light-border' } );
			}
		},

		setMode(mode) {
			this.brandForegroundMode = mode;
		},

		positionFgMarker() {
			const marker = this.$refs.fgMarker;
			if (!marker) return;

			let targetButton;
			if (this.brandForegroundMode === 'auto') {
				targetButton = this.$refs.autoButton;
			} else if (this.brandForegroundMode === 'light') {
				targetButton = this.$refs.lightButton;
			} else {
				targetButton = this.$refs.darkButton;
			}

			if (targetButton) {
				marker.style.width = targetButton.offsetWidth + 'px';
				marker.style.left = targetButton.offsetLeft + 'px';
			}
		},

		getLuminance(hex) {
			if (!hex) return 0;
			if (hex.includes('var(')) {
				const fallback = hex.split(',')[1]?.trim().replace(')', '');
				hex = fallback || '#1f2937';
			}
			const rgb = this.hexToRgb(hex.trim());
			if (!rgb) return 0;

			// WCAG 2.1 relative luminance calculation
			const toLinear = (c) => c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
			return 0.2126 * toLinear(rgb.r / 255) + 0.7152 * toLinear(rgb.g / 255) + 0.0722 * toLinear(rgb.b / 255);
		},

		calculateContrastRatio(fgHex, bgHex) {
			const fgLum = this.getLuminance(fgHex);
			const bgLum = this.getLuminance(bgHex);
			const lighter = Math.max(fgLum, bgLum);
			const darker = Math.min(fgLum, bgLum);
			return (lighter + 0.05) / (darker + 0.05);
		},

		getContrastingTextColor(bgHex) {
			const bgLuminance = this.getLuminance(bgHex);
			const blackContrast = (bgLuminance + 0.05) / (0 + 0.05);
			const whiteContrast = (1 + 0.05) / (bgLuminance + 0.05);

			if (whiteContrast > blackContrast) {
				return '#ffffff';
			}

			return this.deriveDarkForeground(bgHex);
		},

		deriveDarkForeground(hex) {
			const rgb = this.hexToRgb(hex);
			if (!rgb) return '#1f2937';

			const [h, s] = this.rgbToHsl(rgb.r, rgb.g, rgb.b);

			// Achromatic or near-achromatic: use neutral dark gray.
			if (s < 0.1) return '#1f2937';

			const darkS = Math.min(s, 0.9);
			let targetL = 0.18;

			let [dr, dg, db] = this.hslToRgb(h, darkS, targetL);
			let darkHex = this.rgbToHex(dr, dg, db);
			let contrast = this.calculateContrastRatio(darkHex, hex);

			// Darken further if contrast is insufficient.
			while (contrast < 4.5 && targetL > 0.05) {
				targetL -= 0.02;
				[dr, dg, db] = this.hslToRgb(h, darkS, targetL);
				darkHex = this.rgbToHex(dr, dg, db);
				contrast = this.calculateContrastRatio(darkHex, hex);
			}

			if (contrast < 4.5) return '#000000';

			return darkHex;
		},

		rgbToHsl(r, g, b) {
			r /= 255; g /= 255; b /= 255;
			const max = Math.max(r, g, b);
			const min = Math.min(r, g, b);
			const l = (max + min) / 2;

			if (max === min) return [0, 0, l];

			const d = max - min;
			const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
			let h;

			if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
			else if (max === g) h = ((b - r) / d + 2) / 6;
			else h = ((r - g) / d + 4) / 6;

			return [h, s, l];
		},

		hslToRgb(h, s, l) {
			if (s === 0) {
				const v = Math.round(l * 255);
				return [v, v, v];
			}

			const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
			const p = 2 * l - q;

			const hueToChannel = (t) => {
				if (t < 0) t += 1;
				if (t > 1) t -= 1;
				if (t < 1/6) return p + (q - p) * 6 * t;
				if (t < 1/2) return q;
				if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
				return p;
			};

			return [
				Math.round(hueToChannel(h + 1/3) * 255),
				Math.round(hueToChannel(h) * 255),
				Math.round(hueToChannel(h - 1/3) * 255),
			];
		},

		rgbToHex(r, g, b) {
			return '#' + [r, g, b].map(c => c.toString(16).padStart(2, '0')).join('');
		},

		hexToRgb(hex) {
			// Handle shorthand hex
			if (hex.length === 4) {
				hex = '#' + hex[1] + hex[1] + hex[2] + hex[2] + hex[3] + hex[3];
			}
			const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
			return result
				? {
						r: parseInt(result[1], 16),
						g: parseInt(result[2], 16),
						b: parseInt(result[3], 16),
				  }
				: null;
		},

		getFormattedContrastRatio() {
			return this.contrastRatio.toFixed(1) + ':1';
		},

		resetColor() {
			this.color = this.defaultColor;
			// Notify main component of state change for dirty tracking
			window.dispatchEvent(new CustomEvent('rewardswp:settings-changed'));
		},
	}));

	Alpine.data(
		'licenseSection',
		function (
			licenseKey,
			licenseStatus,
			licenseConnected,
			licenseExpiryFormatted,
			licenseExpiry,
			planName,
			priceId,
			ajaxNonce,
			ajaxUrl,
			activateProNonce,
			activateProUrl,
			settingsUrl
		) {
			return {
				isLoading: false,
				isActivatingPro: false,
				showActivateProModal: false,
				licenseKey,
				licenseStatus,
				licenseConnected: Boolean(licenseConnected),
				licenseExpiryFormatted,
				licenseExpiry,
				licenseLevel: planName,
				priceId,
				isUpgrade: false,
				connectLicenseKey() {
					if (!this.licenseKey) {
						return;
					}

					this.isLoading = true;
					this.licenseStatus = '';

					const formData = new FormData();
					formData.append('action', 'rewardswp_connect_license');
					formData.append('_ajax_nonce', ajaxNonce);
					formData.append('license_key', this.licenseKey);

					fetch(ajaxUrl, {
						method: 'POST',
						body: formData,
					})
						.then(function (response) {
							return response.json();
						})
						.then(
							function (response) {
								if (!response.success) {
									this.isUpgrade = response.data?.upgrade ?? false;
									this.licenseConnected = false;
									this.licenseExpiryFormatted = '';
									this.licenseExpiry = '';
									this.licenseStatus = 'invalid';
									this.isLoading = false;

									return;
								}

								this.isLoading = false;

								if ('pro_installed_inactive' === response.data.status) {
									this.licenseStatus = response.data.status;
									this.licenseExpiryFormatted = response.data.expires_formatted;
									this.licenseExpiry = response.data.expires;
									this.licenseConnected = false;
									this.showActivateProModal = true;
									this.isLoading = false;
									return;
								}

								if (response.data.upgrade) {
									window.location = response.data.upgradeUrl;
									return;
								}

								this.licenseStatus = response.data.status;

								if (response.data.status === 'valid') {
									this.licenseConnected = true;
									this.licenseExpiryFormatted = response.data.expires_formatted;
									this.licenseExpiry = response.data.expires;
									this.licenseLevel = response.data.plan_name;
									this.priceId = response.data.price_id;
								} else {
									this.licenseConnected = false;
									this.licenseExpiryFormatted = '';
									this.licenseExpiry = '';
									this.licenseStatus = 'invalid';
								}

								this.$dispatch('license-updated', { requiresUpgrade: false });
							}.bind(this)
						)
						.catch(
							function () {
								this.isLoading = false;
							}.bind(this)
						);
				},

				activateProAndReload() {
					this.isActivatingPro = true;

					const formData = new FormData();
					formData.append('action', 'rewardswp_activate_pro_plugin');
					formData.append('_ajax_nonce', activateProNonce);

					fetch(activateProUrl, {
						method: 'POST',
						body: formData,
					})
						// Success leads to a server-side redirect, so no specific success handling needed here.
						// The browser will automatically follow the redirect.
						.then(function (response) {
							return response.json();
						})
						.then(
							function (response) {
								if (response.success) {
									// Redirect to settings page on success after a short delay, with cache busting
									setTimeout(() => {
										window.location.href =
											settingsUrl +
											(settingsUrl.includes('?') ? '&' : '?') +
											'rewardswp_refresh=' +
											Date.now();
									}, 300); // 300ms delay
								} else {
									// Display error message from backend
									alert(response.data?.message || 'Activation failed.');
									this.isActivatingPro = false; // Stop spinner on failure
								}
							}.bind(this)
						)
						.catch(
							function (error) {
								console.error('Activation AJAX error:', error);
								// Display a generic error or potentially parse the error response if the server sends one on failure
								alert('An error occurred during activation. Please try again.');
								this.isActivatingPro = false;
							}.bind(this)
						);
				},

				disconnectLicenseKey() {
					this.isLoading = true;

					const formData = new FormData();
					formData.append('action', 'rewardswp_disconnect_license');
					formData.append('_ajax_nonce', ajaxNonce);
					formData.append('license_key', this.licenseKey);

					fetch(ajaxUrl, {
						method: 'POST',
						body: formData,
					})
						.then(function (response) {
							return response.json();
						})
						.then(
							function (response) {
								if (!response.success) {
									return;
								}

								this.licenseConnected = false;
								this.isLoading = false;
								this.licenseKey = '';
								this.licenseStatus = '';
								this.licenseExpiry = '';

								this.$dispatch('license-updated', { requiresUpgrade: true });
							}.bind(this)
						)
						.catch(
							function () {
								this.isLoading = false;
							}.bind(this)
						);
				},

				forceRefresh() {
					if (!this.licenseKey) {
						return;
					}

					this.isLoading = true;

					const formData = new FormData();
					formData.append('action', 'rewardswp_connect_license');
					formData.append('_ajax_nonce', ajaxNonce);
					formData.append('license_key', this.licenseKey);
					formData.append('force_refresh', 'true');

					fetch(ajaxUrl, {
						method: 'POST',
						body: formData,
					})
						.then(function (response) {
							return response.json();
						})
						.then(
							function (response) {
								if (!response.success) {
									return;
								}

								this.licenseConnected = true;
								this.isLoading = false;
								this.licenseExpiryFormatted = response.data.expires_formatted;
								this.licenseExpiry = response.data.expires;
								this.licenseStatus = response.data.status;

								this.$dispatch('license-updated', { requiresUpgrade: false });
							}.bind(this)
						)
						.catch(
							function () {
								this.isLoading = false;
							}.bind(this)
						);
				},
			};
		}
	);

	Alpine.data('memberDetailView', (params = {}) => ({
		// Props from PHP
		memberId: params.memberId || null,
		adjustPointsNonce: params.adjustPointsNonce || '',
		currentPointsBalance: params.initialPointsBalance || 0,
		currentLifetimePoints: params.initialLifetimePoints || 0,
		currentSpentPoints: params.initialSpentPoints || 0,
		pointsActivity: [], // Will be populated in init
		activityItemsPerPage: params.activityItemsPerPage || 10,
		ajaxurl: params.ajaxurl || '',
		currentReferralCode: params.initialReferralCode || '',
		referralLinkBase: params.referralLinkBase || '',
		updateReferralCodeNonce: params.updateReferralCodeNonce || '',
		editMemberNonce: params.editMemberNonce || '', // Added for opening edit modal
        currentFullName: params.initialFullName || '', // Added for reactive display
        currentEmail: params.initialEmail || '', // Added for reactive display
        currentGravatarUrl: params.initialGravatarUrl || '', // Added for reactive display
		currentBirthdayDisplay: params.initialBirthdayDisplay || '', // Added for reactive birthday display
		currentBirthdayMonth: params.initialBirthdayMonth || '',
		currentBirthdayDay: params.initialBirthdayDay || '',
		currentMemberStatus: params.initialMemberStatus || 'active', // Added for exclude/include
		currentMemberTypeHtmlBadge: params.initialMemberTypeHtml || '', // Added for reactive badge
		updateStatusNonce: params.updateStatusNonce || '', // Added for exclude/include
		deleteMemberNonce: params.deleteMemberNonce || '', // Added for delete member modal

		// Activity tab state
		activeTab: params.initialActiveTab || 'points',
		activityHighlighted: false,

		init() {
			this.pointsActivity = JSON.parse(params.initialPointsActivityJson || '[]');

			// Handle hash-based tab activation
			const hash = window.location.hash;
			if (hash.startsWith('#activity-')) {
				const tab = hash.replace('#activity-', '');
				if (['points', 'referrals', 'rewards'].includes(tab)) {
					this.activeTab = tab;
					this.$nextTick(() => {
						const activityEl = document.getElementById('activity');
						if (activityEl) {
							activityEl.scrollIntoView({ behavior: 'smooth', block: 'start' });
						}
					});
				}
			}

			this.$nextTick(() => {
				this.initStickyObserver();
			});
			// this.fetchPointsActivity(1); // Initial fetch for page 1 - No longer needed if we handle updates directly

			window.addEventListener('rewardswp-points-adjusted', (event) => {
				this.currentPointsBalance = event.detail.new_balance;
				this.currentLifetimePoints = event.detail.new_lifetime_points;
				this.currentSpentPoints = event.detail.new_spent_points;

				// Prepend the new entry to the pointsActivity array
				if (event.detail.new_entry) {
					this.pointsActivity.unshift(event.detail.new_entry);
				}
			});

			window.addEventListener('rewardswp-referral-code-updated', (event) => {
				this.currentReferralCode = event.detail.newReferralCode;
			});

			// Listen for member details updates
            window.addEventListener('rewardswp-member-details-updated', (event) => {
                this.currentFullName = event.detail.fullName;
                this.currentEmail = event.detail.email;
                this.currentGravatarUrl = event.detail.gravatarUrl;
				if (event.detail.birthdayDisplay !== undefined) {
					this.currentBirthdayDisplay = event.detail.birthdayDisplay;
				}
				if (event.detail.birthdayMonth !== undefined) {
					this.currentBirthdayMonth = event.detail.birthdayMonth;
				}
				if (event.detail.birthdayDay !== undefined) {
					this.currentBirthdayDay = event.detail.birthdayDay;
				}
            });

			window.addEventListener('rewardswp-member-status-updated', (event) => {
				this.currentMemberStatus = event.detail.newStatus;
				if (event.detail.memberTypeHtml) {
					this.currentMemberTypeHtmlBadge = event.detail.memberTypeHtml;
				}
			});

			// Listen for show-activity events (e.g., from "View all" links in cards)
			window.addEventListener('show-activity', (event) => {
				this.activeTab = event.detail.tab;
				const activityEl = document.getElementById('activity');
				if (activityEl) {
					activityEl.scrollIntoView({ behavior: 'smooth', block: 'start' });
				}
				this.activityHighlighted = true;
				setTimeout(() => this.activityHighlighted = false, 1500);
			});
		},

		copyToClipboard(text) {
			return navigator.clipboard
				.writeText(text)
				.then(() => {
					window.dispatchEvent(
						new CustomEvent('rewardswp-toast', {
							detail: { message: 'Copied to clipboard: ' + text, type: 'success' },
						})
					);
					return true;
				})
				.catch((err) => {
					console.error('Failed to copy: ', err);
					window.dispatchEvent(
						new CustomEvent('rewardswp-toast', {
							detail: { message: 'Failed to copy to clipboard.', type: 'error' },
						})
					);
					return false;
				});
		},
		// The handleAdjustPoints logic has been removed from here.
		// The `adjustPointsModal` component is responsible for handling the adjustment via AJAX.
		// This `memberDetailView` component will now listen to an event dispatched by `adjustPointsModal` upon success.

		initStickyObserver() {
			const header = document.getElementById('rewardswp-member-detail-view-header');
			const sentinel = document.getElementById('rewardswp-sticky-sentinel');
			if (!header || !sentinel) return;

			const observer = new IntersectionObserver((entries) => {
				entries.forEach((entry) => {
					if (entry.isIntersecting) {
						header.classList.remove('is-sticky');
					} else {
						header.classList.add('is-sticky');
					}
				});
			});

			observer.observe(sentinel);
		},

		includeMember() {
			if (!this.memberId || !this.updateStatusNonce) return;
			// Add a loading state if you have one, e.g., this.isStatusUpdating = true;

			const formData = new FormData();
			formData.append('action', 'rewardswp_update_member_status');
			formData.append('nonce', this.updateStatusNonce);
			formData.append('member_id', this.memberId);
			formData.append('new_status', 'active');

			fetch(this.ajaxurl, {
				method: 'POST',
				body: formData,
			})
				.then((response) => response.json())
				.then((result) => {
					if (result.success) {
						window.dispatchEvent(
							new CustomEvent('rewardswp-toast', {
								detail: { message: result.data.message, type: 'success' },
							})
						);
						// The 'rewardswp-member-status-updated' event listener will update the status and badge
						// No need to manually set this.currentMemberStatus here if the event is dispatched by the AJAX handler itself.
						// However, the AJAX handler for the modal dispatches it. For consistency, this one should too.
						window.dispatchEvent(
							new CustomEvent('rewardswp-member-status-updated', {
								detail: {
									memberId: this.memberId,
									newStatus: result.data.new_status,
									memberTypeHtml: result.data.member_type_html,
								},
							})
						);
					} else {
						window.dispatchEvent(
							new CustomEvent('rewardswp-toast', {
								detail: {
									message: result.data.message || 'Error including member.',
									type: 'error',
								},
							})
						);
					}
				})
				.catch((error) => {
					console.error('Error including member:', error);
					window.dispatchEvent(
						new CustomEvent('rewardswp-toast', {
							detail: { message: 'Network error. Please try again.', type: 'error' },
						})
					);
				})
				.finally(() => {
					// this.isStatusUpdating = false;
				});
		},
	}));

	Alpine.data('updateReferralCodeModal', (params = {}) => ({
		showModal: false,
		memberId: params.memberId || null,
		initialReferralCode: params.initialReferralCode || '',
		referralCode: params.initialReferralCode || '',
		ajaxNonce: params.ajaxNonce || '',
		ajaxurl: params.ajaxurl || '',
		referralLinkBase: params.referralLinkBase || '',
		text: params.text || {},
		isLoading: false,
		errorMessage: '',
		isCodeValid: true, // Basic validation state

		init() {
			window.addEventListener('open-update-referral-code-modal', (event) => {
				// Optionally, reset to initial code if modal is reopened, or pass new initial code
				// For now, let's assume params.initialReferralCode is the most current when modal is setup
				this.referralCode = this.initialReferralCode;
				this.memberId = event.detail.memberId || params.memberId; // Allow overriding memberId if needed
				this.initialReferralCode =
					event.detail.currentReferralCode || params.initialReferralCode;
				this.referralCode = event.detail.currentReferralCode || params.initialReferralCode;
				this.ajaxNonce = event.detail.nonce || params.ajaxNonce;
				this.errorMessage = '';
				this.isLoading = false;
				this.validateCode();
				this.showModal = true;
			});
		},

		generateRandomCode(length = 8) {
			const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
			let result = '';
			let hasLetter = false;
			const charactersLength = characters.length;
			for (let i = 0; i < length; i++) {
				const char = characters.charAt(Math.floor(Math.random() * charactersLength));
				result += char;
				if (/[a-zA-Z]/.test(char)) {
					hasLetter = true;
				}
			}
			// Ensure at least one letter, similar to PHP's generate_custom_hash
			if (!hasLetter && length > 0) {
				// Replace a random character with a letter
				const letterPos = Math.floor(Math.random() * length);
				const letters = 'abcdefghijklmnopqrstuvwxyz';
				const randomLetter = letters.charAt(Math.floor(Math.random() * letters.length));
				result =
					result.substring(0, letterPos) + randomLetter + result.substring(letterPos + 1);
			}
			this.referralCode = result;
			this.validateCode();
		},

		validateCode() {
			this.errorMessage = ''; // Clear previous server errors
			if (!this.referralCode.trim()) {
				this.isCodeValid = false;
				// No immediate error message, rely on disabled button and potential server error
				return;
			}
			// Check if the code is numeric only
			const numericOnlyRegex = /^\d+$/;
			if (numericOnlyRegex.test(this.referralCode.trim())) {
				this.errorMessage =
					this.text.validationErrorNumericCode || 'Referral code cannot be numeric only.';
				this.isCodeValid = false;
				return;
			}
			// Add any other client-side validation rules here (e.g., length, characters)
			// For example: if (this.referralCode.length < 5 || this.referralCode.length > 12)
			this.isCodeValid = true;
		},

		async handleUpdateReferralCode() {
			this.validateCode();
			if (
				!this.isCodeValid ||
				this.isLoading ||
				this.referralCode === this.initialReferralCode
			) {
				return;
			}

			this.isLoading = true;
			this.errorMessage = '';

			const formData = new FormData();
			formData.append('action', 'rewardswp_update_member_referral_code');
			formData.append('nonce', this.ajaxNonce);
			formData.append('member_id', this.memberId);
			formData.append('new_referral_code', this.referralCode);

			try {
				const response = await fetch(this.ajaxurl, {
					method: 'POST',
					body: formData,
				});

				const result = await response.json();

				if (result.success) {
					this.showModal = false;
					// Dispatch a global event for the toast
					window.dispatchEvent(
						new CustomEvent('rewardswp-toast', {
							detail: {
								message:
									this.text.codeUpdatedSuccess ||
									'Referral code updated successfully.',
								type: 'success',
							},
						})
					);
					// Dispatch an event for the MemberDetailView to update its data
					window.dispatchEvent(
						new CustomEvent('rewardswp-referral-code-updated', {
							detail: {
								memberId: this.memberId,
								newReferralCode: result.data.new_referral_code,
							},
						})
					);
					this.initialReferralCode = result.data.new_referral_code; // Update initial code for next open
					// isLoading will be reset by the modal closing and re-opening, or if an error occurs.
				} else {
					this.errorMessage =
						result.data?.message || this.text.genericError || 'An error occurred.';
					this.isLoading = false; // Reset loading on error
				}
			} catch (error) {
				this.errorMessage =
					this.text.networkError ||
					'A network error occurred. Please check your connection.';
				console.error('Error updating referral code:', error);
				this.isLoading = false; // Reset loading on error
			} // finally block removed, isLoading is handled in success/error explicitly or by modal re-init
		},
	}));

	Alpine.data('listUpdateReferralCodeModal', (params = {}) => ({
		showModal: false,
		memberId: params.memberId || null,
		initialReferralCode: params.initialReferralCode || '',
		referralCode: params.initialReferralCode || '',
		ajaxNonce: params.ajaxNonce || '',
		ajaxurl: params.ajaxurl || '',
		referralLinkBase: params.referralLinkBase || '',
		text: params.text || {},
		isLoading: false,
		errorMessage: '',
		isCodeValid: true, // Basic validation state

		init() {
			window.addEventListener('open-list-update-referral-code-modal', (event) => {
				this.memberId = event.detail.memberId || params.memberId;
				this.initialReferralCode =
					event.detail.currentReferralCode || params.initialReferralCode;
				this.referralCode = event.detail.currentReferralCode || params.initialReferralCode;
				this.ajaxNonce = event.detail.nonce || params.ajaxNonce;
				this.errorMessage = '';
				this.isLoading = false;
				this.validateCode();
				this.showModal = true;
			});
		},

		generateRandomCode(length = 8) {
			const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
			let result = '';
			let hasLetter = false;
			const charactersLength = characters.length;
			for (let i = 0; i < length; i++) {
				const char = characters.charAt(Math.floor(Math.random() * charactersLength));
				result += char;
				if (/[a-zA-Z]/.test(char)) {
					hasLetter = true;
				}
			}
			if (!hasLetter && length > 0) {
				const letterPos = Math.floor(Math.random() * length);
				const letters = 'abcdefghijklmnopqrstuvwxyz';
				const randomLetter = letters.charAt(Math.floor(Math.random() * letters.length));
				result =
					result.substring(0, letterPos) + randomLetter + result.substring(letterPos + 1);
			}
			this.referralCode = result;
			this.validateCode();
		},

		validateCode() {
			this.errorMessage = '';
			if (!this.referralCode.trim()) {
				this.isCodeValid = false;
				return;
			}
			const numericOnlyRegex = /^\d+$/;
			if (numericOnlyRegex.test(this.referralCode.trim())) {
				this.errorMessage =
					this.text.validationErrorNumericCode || 'Referral code cannot be numeric only.';
				this.isCodeValid = false;
				return;
			}
			this.isCodeValid = true;
		},

		async handleUpdateReferralCode() {
			this.validateCode();
			if (
				!this.isCodeValid ||
				this.isLoading ||
				this.referralCode === this.initialReferralCode
			) {
				return;
			}

			this.isLoading = true;
			this.errorMessage = '';

			const formData = new FormData();
			formData.append('action', 'rewardswp_update_member_referral_code');
			formData.append('nonce', this.ajaxNonce);
			formData.append('member_id', this.memberId);
			formData.append('new_referral_code', this.referralCode);

			try {
				const response = await fetch(this.ajaxurl, {
					method: 'POST',
					body: formData,
				});

				const result = await response.json();

				if (result.success) {
					this.showModal = false;
					window.dispatchEvent(
						new CustomEvent('rewardswp-toast', {
							detail: {
								message:
									this.text.codeUpdatedSuccess ||
									'Referral code updated successfully.',
								type: 'success',
							},
						})
					);
					// No need to dispatch 'rewardswp-referral-code-updated' in the list view
					this.initialReferralCode = result.data.new_referral_code;
				} else {
					this.errorMessage =
						result.data?.message || this.text.genericError || 'An error occurred.';
					this.isLoading = false;
				}
			} catch (error) {
				this.errorMessage =
					this.text.networkError ||
					'A network error occurred. Please check your connection.';
				console.error('Error updating referral code:', error);
				this.isLoading = false;
			}
		},
	}));

	Alpine.data('editMemberModal', (params = {}) => ({
		showModal: false,
		memberId: params.memberId || null,
		ajaxNonce: params.ajaxNonce || '',
		ajaxurl: params.ajaxurl || '',
		text: params.text || {},
		months: params.months || [],

		initialFullName: params.initialFullName || '',
		initialEmail: params.initialEmail || '',
		initialBirthdayMonth: params.initialBirthdayMonth || '',
		initialBirthdayDay: params.initialBirthdayDay || '',

		editableFullName: '',
		editableEmail: '',
		editableBirthdayMonth: '',
		editableBirthdayDay: '',

		isLoading: false,
		errors: {
			// To store field-specific errors
			fullName: '',
			email: '',
			birthday: '',
		},
		generalError: '',

		get daysInMonth() {
			if (!this.editableBirthdayMonth) return Array.from({ length: 31 }, (_, i) => i + 1);
			const month = parseInt(this.editableBirthdayMonth);
			const daysMap = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
			const max = daysMap[month - 1] || 31;
			return Array.from({ length: max }, (_, i) => i + 1);
		},

		init() {
			this.resetForm(); // Initialize with current values
			window.addEventListener('open-edit-member-modal', (event) => {
				this.memberId = event.detail.memberId;
				this.ajaxNonce = event.detail.nonce;
				this.initialFullName = event.detail.fullName;
				this.initialEmail = event.detail.email;
				this.initialBirthdayMonth = event.detail.birthdayMonth || '';
				this.initialBirthdayDay = event.detail.birthdayDay || '';
				this.resetForm();
				this.showModal = true;
			});

			this.$watch('editableBirthdayMonth', () => {
				// Reset day if it exceeds the new month's max
				const maxDay = this.daysInMonth.length;
				if (parseInt(this.editableBirthdayDay) > maxDay) {
					this.editableBirthdayDay = '';
				}
			});
		},

		resetForm() {
            this.editableFullName = this.initialFullName;
            this.editableEmail = this.initialEmail;
			this.editableBirthdayMonth = this.initialBirthdayMonth;
			this.editableBirthdayDay = this.initialBirthdayDay;
			this.clearErrors();
			this.isLoading = false;
		},

		closeModal() {
			this.showModal = false;
			// It's good practice to reset the form on close in case it was left in an invalid state
			// Or at least clear errors if they don't want to lose typed data.
			// For now, let's just clear errors.
			this.clearErrors();
		},

		clearErrors() {
            this.errors.fullName = '';
            this.errors.email = '';
			this.errors.birthday = '';
			this.generalError = '';
		},

		isDirty() {
            return (
                this.editableFullName !== this.initialFullName ||
                this.editableEmail !== this.initialEmail ||
				this.editableBirthdayMonth !== this.initialBirthdayMonth ||
				this.editableBirthdayDay !== this.initialBirthdayDay
            );
		},

		validateEmail(email) {
			const re =
				/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
			return re.test(String(email).toLowerCase());
		},

		validateForm() {
			this.clearErrors();
			let isValid = true;

			if (!this.editableFullName.trim()) {
				this.errors.fullName = this.text.validationErrorFullName || 'Name cannot be empty.'; // Provide default if not in text
				isValid = false;
			} else if (this.editableFullName.trim().indexOf(' ') === -1) {
				// Check for at least one space
				this.errors.fullName =
					this.text.validationErrorFullNameFormat ||
					'Please enter both first and last name.';
				isValid = false;
			}

			if (!this.editableEmail.trim()) {
				this.errors.email = this.text.validationErrorEmailEmpty || 'Email cannot be empty.';
				isValid = false;
			} else if (!this.validateEmail(this.editableEmail)) {
				this.errors.email =
					this.text.validationErrorEmailInvalid || 'Please enter a valid email address.';
				isValid = false;
			}

			return isValid;
		},

		hasErrors() {
			return Object.values(this.errors).some((error) => error !== '');
		},

		handleSaveChanges() {
			if (!this.validateForm()) {
				this.generalError =
					this.text.validationError || 'Please check the highlighted fields.';
				return;
			}
			this.isLoading = true;
			this.generalError = '';

			const formData = new URLSearchParams();
			formData.append('action', 'rewardswp_update_member_details');
			formData.append('nonce', this.ajaxNonce);
			formData.append('member_id', this.memberId);
            formData.append('full_name', this.editableFullName);
            formData.append('email', this.editableEmail);
			if (this.editableBirthdayMonth && this.editableBirthdayDay) {
				formData.append('birthday_month', this.editableBirthdayMonth);
				formData.append('birthday_day', this.editableBirthdayDay);
			}

			fetch(this.ajaxurl, {
				method: 'POST',
				body: formData,
			})
				.then((response) => response.json())
				.then((data) => {
					if (data.success) {
						this.showModal = false;
						window.dispatchEvent(
							new CustomEvent('rewardswp-show-toast', {
								detail: {
									type: 'success',
									message:
										this.text.memberUpdateSuccess || 'Member details updated!',
								},
							})
						);
						// Dispatch an event with the updated data so MemberDetailView can update itself
						window.dispatchEvent(
							new CustomEvent('rewardswp-member-details-updated', {
								detail: {
                                    fullName: data.data.full_name,
                                    email: data.data.email,
                                    gravatarUrl: data.data.gravatar_url_large, // If email changes, gravatar might too
									birthdayDisplay: data.data.birthday_display || '',
									birthdayMonth: this.editableBirthdayMonth || '',
									birthdayDay: this.editableBirthdayDay || '',
								},
							})
						);
						// Update initial values so isDirty() works correctly if modal is reopened
                        this.initialFullName = data.data.full_name;
                        this.initialEmail = data.data.email;
						if (this.editableBirthdayMonth && this.editableBirthdayDay) {
							this.initialBirthdayMonth = this.editableBirthdayMonth;
							this.initialBirthdayDay = this.editableBirthdayDay;
						}
					} else {
						// Potentially handle field-specific errors from backend if provided
						if (data.data.errors) {
							for (const field in data.data.errors) {
								if (this.errors.hasOwnProperty(field)) {
									this.errors[field] = data.data.errors[field];
								}
							}
							// If there were specific field errors, set a general error message too.
							this.generalError =
								this.text.validationError || 'Please check the highlighted fields.';
						} else {
							// If no specific field errors, use the message from backend as general error, or a default.
							this.generalError = data.data.message || this.text.genericError;
						}
					}
				})
				.catch((error) => {
					this.generalError = this.text.networkError;
					console.error('Error:', error);
				})
				.finally(() => {
					this.isLoading = false;
				});
		},
	}));

	Alpine.data('adjustPointsModal', (params) => ({
		// Core state
		showAdjustPointsModal: false,
		showConfirmationDialog: false,
		adjustPointsAction: 'add',
		adjustPointsAmount: '',
		adjustPointsReason: '',
		isLoading: false,

		// Validation state
		touchedAmount: false,
		touchedReason: false,
		errorMessage: '',
		reasonError: '',

		// Configuration
		member_id: params.member_id,
		member_name: params.member_name,
		current_balance: params.current_balance,
		large_deduction_threshold: params.large_deduction_threshold,
		nonce: params.nonce,

		// Error messages
		errorMessageAmount: params.errorMessageAmount,
		errorMessageReasonRequired: params.errorMessageReasonRequired,

		// Translatable text
		textAdd: params.textAdd,
		textDeduct: params.textDeduct,
		textPoints: params.textPoints,
		successMessage: params.successMessage,
		errorMessageGeneric: params.errorMessageGeneric,
		errorMessageNetwork: params.errorMessageNetwork,

		// Watch for modal open to reset form
		init() {
			window.addEventListener('open-adjust-points-modal', (event) => {
				const detail = event.detail;
				this.member_id = detail.memberId;
				this.member_name = detail.memberName;
				this.current_balance = detail.currentBalance;
				this.nonce = detail.nonce;
				this.showAdjustPointsModal = true;
			});

			this.$watch('showAdjustPointsModal', (value) => {
				if (value) {
					// Reset form fields when modal opens
					this.adjustPointsAmount = '';
					this.adjustPointsAction = 'add';
					this.adjustPointsReason = '';
					this.touchedAmount = false;
					this.touchedReason = false;
					this.errorMessage = '';
					this.reasonError = '';
					this.showConfirmationDialog = false;
					this.isLoading = false;
				}
			});
		},

		// Segmented control marker positioning
		repositionMarker(button) {
			if (this.$refs.marker && button) {
				this.$refs.marker.style.width = button.offsetWidth + 'px';
				this.$refs.marker.style.height = button.offsetHeight + 'px';
				this.$refs.marker.style.left = button.offsetLeft + 'px';
			}
		},

		// Initialize marker position and focus amount field when modal opens
		initializeMarker() {
			this.$nextTick(() => {
				if (this.adjustPointsAction === 'add' && this.$refs.addButton) {
					this.repositionMarker(this.$refs.addButton);
				} else if (this.adjustPointsAction === 'deduct' && this.$refs.deductButton) {
					this.repositionMarker(this.$refs.deductButton);
				}

				// Auto-focus the points amount field
				if (this.$refs.pointsAmountField) {
					this.$refs.pointsAmountField.focus();
				}
			});
		},

		// Set action with validation
		setAction(action) {
			this.adjustPointsAction = action;
			this.validateReason();
		},

		// Amount validation
		validateAmount() {
			this.errorMessage = '';

			if (!this.adjustPointsAmount) {
				return;
			}

			const amount = parseInt(this.adjustPointsAmount);
			if (isNaN(amount) || amount <= 0) {
				this.errorMessage = this.errorMessageAmount;
				return;
			}

			// Check for non-numeric characters
			if (!/^\d+$/.test(this.adjustPointsAmount)) {
				this.errorMessage = this.errorMessageAmount;
				return;
			}
		},

		// Reason validation (required for deduct)
		validateReason() {
			this.reasonError = '';

			if (this.adjustPointsAction === 'deduct' && !this.adjustPointsReason.trim()) {
				this.reasonError = this.errorMessageReasonRequired;
				return;
			}
		},

		// Calculate new balance
		getNewBalance() {
			if (!this.adjustPointsAmount || !this.adjustPointsAction) {
				return this.current_balance;
			}

			const amount = parseInt(this.adjustPointsAmount);
			if (isNaN(amount)) {
				return this.current_balance;
			}

			if (this.adjustPointsAction === 'add') {
				return this.current_balance + amount;
			} else {
				return Math.max(0, this.current_balance - amount);
			}
		},

		// Adaptive CTA text
		getCTAText() {
			const action = this.adjustPointsAction === 'add' ? this.textAdd : this.textDeduct;
			return `${action} ${this.textPoints}`;
		},

		// Form validation
		isFormValid() {
			// Must have action
			if (!this.adjustPointsAction) {
				return false;
			}

			// Must have valid amount
			if (!this.adjustPointsAmount || this.errorMessage) {
				return false;
			}

			// Must have reason for deduct
			if (this.adjustPointsAction === 'deduct' && !this.adjustPointsReason.trim()) {
				return false;
			}

			return true;
		},

		// Handle adjustment with confirmation for large deductions
		handleAdjustPoints() {
			// Validate first
			this.validateAmount();
			this.validateReason();

			if (!this.isFormValid()) {
				return;
			}

			// Check for large deduction threshold
			const amount = parseInt(this.adjustPointsAmount);
			if (this.adjustPointsAction === 'deduct' && amount > this.large_deduction_threshold) {
				this.showConfirmationDialog = true;
				return;
			}

			this.submitAdjustment();
		},

		// Confirm large deduction
		confirmAdjustment() {
			// Submit directly without changing modal state first
			this.submitAdjustment();
		},

		// Submit the adjustment
		submitAdjustment() {
			this.isLoading = true;

			const formData = new FormData();
			formData.append('action', 'rewardswp_adjust_member_points');
			formData.append('nonce', this.nonce);
			formData.append('member_id', this.member_id);
			formData.append('adjust_action', this.adjustPointsAction);
			formData.append('points_amount', this.adjustPointsAmount);
			formData.append('reason', this.adjustPointsReason);

			fetch(ajaxurl, {
				method: 'POST',
				body: formData,
			})
				.then((response) => response.json())
				.then((data) => {
					this.isLoading = false;

					if (data.success) {
						// Close the modal first
						this.closeModal();

						// Dispatch an event with the new points data so memberDetailView can update
						window.dispatchEvent(
							new CustomEvent('rewardswp-points-adjusted', {
								detail: {
									new_balance: data.data.new_balance,
									new_lifetime_points: data.data.new_lifetime_points,
									new_spent_points: data.data.new_spent_points,
									new_redeemed_points: data.data.new_redeemed_points,
									new_entry: data.data.new_entry,
								},
							})
						);

						// Also, directly update the points in the members list table if it exists
						const balanceSpan = document.getElementById(
							`member-points-balance-${this.member_id}`
						);
						if (balanceSpan) {
							balanceSpan.innerText = data.data.new_balance.toLocaleString();
						}

						// Then show the success toast
						window.dispatchEvent(
							new CustomEvent('rewardswp-toast', {
								detail: {
									type: 'success',
									message: data.message || this.successMessage,
								},
							})
						);
					} else {
						// Show error message using the same toast system
						window.dispatchEvent(
							new CustomEvent('rewardswp-toast', {
								detail: {
									type: 'error',
									message:
										data.data && data.data.message
											? data.data.message
											: this.errorMessageGeneric,
								},
							})
						);
					}
				})
				.catch((error) => {
					this.isLoading = false;
					window.dispatchEvent(
						new CustomEvent('rewardswp-toast', {
							detail: {
								type: 'error',
								message: this.errorMessageNetwork,
							},
						})
					);
				});
		},

		// Close modal (form reset handled by init() watcher)
		closeModal() {
			this.showAdjustPointsModal = false;
		},
	}));
});

document.addEventListener('DOMContentLoaded', function () {
	// Initialize main admin header.
	initStickyHeader('rewardswp-admin-header-sentinel', 'rewardswp-admin-header');

	// Initialize settings header.
	initStickyHeader('rewardswp-admin-settings-sentinel', 'rewardswp-admin-settings-header', [
		'bg-white',
	]);

	// Initialize global tooltips for all elements with data-tippy-content attribute.
	tippy('[data-tippy-content]', {
		theme: 'light-border',
		animation: 'scale-subtle',
		arrow: true,
		delay: [100, 0],
		allowHTML: true,
		interactive: true,
		appendTo: document.body,
		onCreate(instance) {
			instance.popper.querySelector('.tippy-content').classList.add(
				'rewardswp-container',
				'[&_p]:mb-3',
				'[&_p]:mt-0',
				'[&_p:last-child]:mb-0',
				'[&_p:has(>strong:only-child)]:mb-1',
				'[&_p:has(>strong:only-child):not(:first-child)]:mt-3'
			);
		}
	});

	// Global listener for member-deleted event
	document.addEventListener('member-deleted', function (event) {
		const memberId = event.detail.memberId;
		if (memberId) {
			// Assuming rows in MembersListTableView have an ID like "member-123"
			// WP_List_Table usually generates id like "post-123" or "user-123".
			// We need to ensure MembersListTableView sets this.
			// Let's assume it's `id="member-${memberId}"` for the <tr>.
			const row = document.getElementById('member-' + memberId);
			if (row) {
				row.style.transition = 'opacity 0.5s ease-out, transform 0.5s ease-out';
				row.style.opacity = '0';
				row.style.transform = 'translateX(-20px)'; // Optional: slide out effect
				setTimeout(() => {
					row.remove();
					// You might want to add a notification here or update counts if the table isn't reloaded.
					// For now, just removing the row. A message like "Member deleted." can be handled
					// by the main page upon receiving a status query arg after a potential (but not forced here) redirect.
				}, 500);
			}
		}
	});
});

// Define custom elements (rewards-panel and rewards-launcher)
defineComponents();

// Register components immediately (before DOM ready)
// This ensures Alpine is ready when the HTML with x-data attributes loads
registerRewardsPanelComponents(Alpine);
registerSetupTasks(Alpine);
registerMergeTagsDropdown(Alpine);
registerMediaUpload(Alpine);
registerDrawerSections(Alpine);
registerDeepLinkHandler(Alpine);
initColorPicker(Alpine);

// Initialize stores with backend data after Alpine starts.
document.addEventListener('alpine:initialized', () => {
	// Initialize global config store.
	if (window.rewardswpConfig && Alpine.store('config')) {
		Alpine.store('config').setup(window.rewardswpConfig);
	}

	// Initialize earn actions store.
	const earnData = window.rewardswpDrawerData?.earn;
	if (earnData && Alpine.store('earnActions')) {
		Alpine.store('earnActions').setup(earnData);
	} else {
		console.warn('[ALPINE INIT] earnData or earnActions store not available', {earnData, storeExists: !!Alpine.store('earnActions')});
	}

	// Initialize spend actions store.
	const spendData = window.rewardswpDrawerData?.spend;
	if (spendData && Alpine.store('spendActions')) {
		Alpine.store('spendActions').setup(spendData);
	} else {
		console.warn('[ALPINE INIT] spendData or spendActions store not available', {spendData, storeExists: !!Alpine.store('spendActions')});
	}

	// Initialize referrals store.
	const referralsData = window.rewardswpDrawerData?.referrals;
	if (referralsData && Alpine.store('referrals')) {
		Alpine.store('referrals').setup(referralsData);
	}
});

// Add global helper functions for reward summary
window.getDisplayRewardTitle = function (reward, rewardType) {
	if (!reward) return '';

	const referralsData = window.rewardswpDrawerData?.referrals;
	const spendData = window.rewardswpDrawerData?.spend;
	const currencySymbol = referralsData?.currencySymbol || spendData?.currencySymbol || '$';

	// If reward has a custom name, use it
	if (reward.name && reward.name.trim() !== '') {
		return reward.name;
	}

	// Generate dynamic title based on reward type and amount
	// Support both referrals (reward_amount) and spend actions (coupon_amount)
	const rewardAmount = reward.reward_amount || reward.coupon_amount || 0;

	// Helper to determine the scope of the reward (what it applies to)
	const getRewardScope = () => {
		// Check for category restrictions (future feature)
		if (reward.categories_included && reward.categories_included.length > 0) {
			// If category names are available, use them
			if (reward.category_names && reward.category_names.length > 0) {
				const categoryList =
					reward.category_names.length <= 3
						? reward.category_names.join(', ')
						: `${reward.category_names.slice(0, 2).join(', ')} and ${
								reward.category_names.length - 2
						  } more`;
				return `products in ${categoryList}`;
			}
			return 'selected categories';
		}

		// Check for specific product restrictions (current feature, but not yet used in UI)
		if (reward.products_included && reward.products_included.length > 0) {
			// For free_product type, this is the product being given away
			// For discounts, this would be product restrictions
			if (reward.type !== 'free_product') {
				if (reward.products_included.length === 1) {
					const productName = window.getRewardProductName(reward.products_included[0]);
					return productName !== reward.products_included[0]
						? productName
						: 'selected product';
				}
				return `${reward.products_included.length} selected products`;
			}
		}

		// No restrictions - applies to entire order
		return 'entire order';
	};

	switch (reward.type) {
		case 'percentage_discount':
			return `${rewardAmount}% off ${getRewardScope()}`;

		case 'fixed_discount':
			return `${applyCurrencySymbol(currencySymbol, rewardAmount)} off ${getRewardScope()}`;

		case 'free_shipping':
			// Free shipping typically applies to entire order
			return 'Free shipping';

		case 'free_product':
			// Check if product_value is set (limits the discount amount)
			const productValue = parseFloat(reward.product_value || 0);
			const hasValueLimit = productValue > 0;

			// Helper to format currency without .00 decimals
			const formatCurrencyValue = (amount) => {
				const numAmount = parseFloat(amount) || 0;
				const formatted =
					Number.isInteger(numAmount) || numAmount === Math.floor(numAmount)
						? `${Math.floor(numAmount)}`
						: numAmount.toFixed(2);
				return applyCurrencySymbol(currencySymbol, formatted);
			};

			// Get product name - priority: products_included[0].name (saved name) > getRewardProductName (cache lookup)
			let freeProductName = '';
			if (reward.products_included && reward.products_included.length > 0) {
				const productEntry = reward.products_included[0];
				// Use the name saved in products_included directly (matches card behavior)
				if (typeof productEntry === 'object' && productEntry.name) {
					freeProductName = productEntry.name;
				} else {
					// Fallback to cache lookup for ID-only format
					freeProductName = window.getRewardProductName(productEntry);
					// If getRewardProductName returned the same value (ID), it means no name was found
					if (freeProductName === productEntry) {
						freeProductName = '';
					}
				}
			}

			if (freeProductName) {
				if (hasValueLimit) {
					return `${formatCurrencyValue(productValue)} off ${freeProductName}`;
				}
				return `Free ${freeProductName}`;
			}

			// Fallback when no product name
			if (hasValueLimit) {
				return `${formatCurrencyValue(productValue)} off product`;
			}
			return 'Free product';

		case 'points': {
			const pointsCurrencySingular = referralsData?.currencySingular || 'Point';
			const pointsCurrencyPlural = referralsData?.currencyPlural || 'Points';
			const pointsLabel = rewardAmount === 1 ? pointsCurrencySingular : pointsCurrencyPlural;
			return `${rewardAmount} ${pointsLabel} per referral`;
		}

		default:
			// Fallback to default titles
			const defaultTitles = referralsData?.rewardTitles ||
				spendData?.rewardTitles || {
					fixed_discount: 'Fixed Amount Discount',
					percentage_discount: 'Percentage Discount',
					free_shipping: 'Free Shipping',
					free_product: 'Free Product',
					points: 'Points Reward',
				};
			return defaultTitles[reward.type] || reward.type;
	}
};

window.currencySymbol = (function () {
	const referralsData = window.rewardswpDrawerData?.referrals;
	const spendData = window.rewardswpDrawerData?.spend;
	return referralsData?.currencySymbol || spendData?.currencySymbol || '$';
})();

// Add earn action summary helper
window.getEarnActionSummary = function (action) {
	if (!action) return '';

	const earnData = window.rewardswpDrawerData?.earn;
	const currencySingular = earnData?.currencySingular || 'Point';
	const currencyPlural = earnData?.currencyPlural || 'Points';
	const currencySymbol = earnData?.currencySymbol || '$';
	const templates = earnData?.summaryTemplates || {};

	// Helper to replace PHP-style placeholders (%1$s, %2$s, etc.)
	const sprintf = (template, ...args) => {
		if (!template) return '';
		return template.replace(/%(\d+)\$s/g, (match, index) => args[parseInt(index, 10) - 1] ?? '').replace(/%%/g, '%');
	};

	// For purchase action (points per dollar)
	if (action.id === 'purchase') {
		const pointsPerDollar = action.points_per_dollar || 0;
		const pointsWord =
			pointsPerDollar === 1 ? currencySingular.toLowerCase() : currencyPlural.toLowerCase();
		// Use base_amount if set, otherwise use currency-appropriate default
		const baseAmount = action.base_amount || earnData?.defaultBaseAmount || 1;
		const formattedBaseAmount = baseAmount.toLocaleString();
		return sprintf(
			templates.purchase,
			pointsPerDollar,
			pointsWord,
			applyCurrencySymbol(currencySymbol, formattedBaseAmount)
		);
	}

	// For fixed amount actions (signup, first_order, product_review, referral, etc.)
	if (action.points !== undefined && action.points !== null) {
		const points = action.points || 0;
		const pointsWord = points === 1 ? currencySingular : currencyPlural;

		// Use specific template if available, otherwise generic
		const template = templates[action.id] || templates.generic;
		if (template) {
			return sprintf(template, points, pointsWord);
		}
	}

	// Fallback
	return templates.fallback || '';
};

// Add spend action summary helper
window.getSpendActionSummary = function (reward) {
	if (!reward) return '';

	const spendData = window.rewardswpDrawerData?.spend;
	const currencySingular = spendData?.currencySingular || 'Point';
	const currencyPlural = spendData?.currencyPlural || 'Points';
	const currencySymbol = spendData?.currencySymbol || '$';
	const templates = spendData?.summaryTemplates || {};

	const points = reward.points_required || reward.points || 0;
	const pointsWord = points === 1 ? currencySingular : currencyPlural;
	const amount = reward.coupon_amount || 0;

	// Helper to replace PHP-style placeholders (%1$s, %2$s, etc.)
	const sprintf = (template, ...args) => {
		if (!template) return '';
		return template.replace(/%(\d+)\$s/g, (match, index) => args[parseInt(index, 10) - 1] ?? '').replace(/%%/g, '%');
	};

	switch (reward.type) {
		case 'fixed_discount':
			return sprintf(templates.fixed_discount, points, pointsWord, applyCurrencySymbol(currencySymbol, amount));
		case 'percentage_discount':
			return sprintf(templates.percentage_discount, points, pointsWord, amount);
		case 'free_shipping':
			return sprintf(templates.free_shipping, points, pointsWord);
		case 'free_product':
			let productName = '';
			if (reward.reward_description && reward.reward_description.trim()) {
				productName = reward.reward_description;
			} else if (reward.products_included && reward.products_included.length > 0 && reward.products_included[0].name) {
				productName = reward.products_included[0].name;
			}
			if (productName) {
				return sprintf(templates.free_product, points, pointsWord, productName);
			}
			return sprintf(templates.free_product_generic, points, pointsWord);
		default:
			return sprintf(templates.free_product_generic, points, pointsWord);
	}
};

window.getRewardProductName = function (productIdOrObject) {
	// Handle both formats:
	// 1. Object format from spend actions: {id: 14, name: "iPhone"}
	// 2. Integer ID format from referrals: 14

	// If it's an object with name, return the name directly
	if (productIdOrObject && typeof productIdOrObject === 'object' && productIdOrObject.name) {
		return productIdOrObject.name;
	}

	// Extract ID from object if needed
	const productId = (productIdOrObject && typeof productIdOrObject === 'object' && productIdOrObject.id)
		? productIdOrObject.id
		: productIdOrObject;

	// Try to get from drawer's products_cache first (for referrals in drawer)
	const drawerStore = typeof Alpine !== 'undefined' ? Alpine.store('drawer') : null;
	if (drawerStore?.selectedAction?.products_cache) {
		const cachedProduct = drawerStore.selectedAction.products_cache.find(
			(p) => p.id === productId || p.id === parseInt(productId, 10)
		);
		if (cachedProduct?.name) {
			return cachedProduct.name;
		}
	}

	// Fall back to products list from drawer data
	const referralsData = window.rewardswpDrawerData?.referrals;
	const spendData = window.rewardswpDrawerData?.spend;

	const products = referralsData?.products || spendData?.products;
	if (products && products[productId]) {
		return products[productId].name;
	}
	return productId;
};

// Start Alpine after setting up event listeners
Alpine.start();

/**
 * Initialize preview tooltips for disabled elements in widget preview.
 * This only runs in admin context for the settings preview.
 */
function initWidgetPreviewTooltips() {
	const webComponent = document.querySelector('rewards-panel');
	if (!webComponent || !webComponent.shadowRoot) {
		return;
	}

	const container = webComponent.shadowRoot;
	const previewElements = container.querySelectorAll('[data-preview-disabled]');

	if (previewElements.length > 0) {
		previewElements.forEach(el => {
			// Skip if already has tippy instance
			if (el._tippy) {
				return;
			}

			const content = el.getAttribute('data-tippy-content') || 'Preview only';
			tippy(el, {
				content: content,
				theme: 'light-border',
				animation: 'scale-subtle',
				arrow: true,
				delay: [100, 0],
				placement: 'top',
				appendTo: document.body,
				popperOptions: {
					strategy: 'fixed',
				},
			});
		});
	}
}

/**
 * Refresh preview tooltips - destroy existing and reinitialize.
 */
function refreshWidgetPreviewTooltips() {
	const webComponent = document.querySelector('rewards-panel');
	if (!webComponent || !webComponent.shadowRoot) {
		return;
	}

	const container = webComponent.shadowRoot;

	// Destroy existing tooltips
	const existingElements = container.querySelectorAll('[data-preview-disabled]');
	existingElements.forEach(el => {
		if (el._tippy) {
			el._tippy.destroy();
		}
	});

	// Wait for DOM to update then reinitialize
	setTimeout(() => {
		initWidgetPreviewTooltips();
	}, 300);
}

// Initialize tooltips when widget preview is ready
document.addEventListener('DOMContentLoaded', function() {
	// Wait for web component and Alpine to initialize
	setTimeout(() => {
		initWidgetPreviewTooltips();

		// Set up MutationObserver to detect new elements in shadow DOM
		const webComponent = document.querySelector('rewards-panel');
		if (webComponent && webComponent.shadowRoot) {
			const observer = new MutationObserver(() => {
				// Debounce tooltip initialization
				clearTimeout(window._tooltipDebounce);
				window._tooltipDebounce = setTimeout(() => {
					initWidgetPreviewTooltips();
				}, 100);
			});

			observer.observe(webComponent.shadowRoot, {
				childList: true,
				subtree: true,
				attributes: true,
				attributeFilter: ['data-preview-disabled']
			});
		}
	}, 500);

	// Listen for state changes to refresh tooltips
	window.addEventListener('switch-preview-member-state', () => {
		refreshWidgetPreviewTooltips();
	});
});

// Helper to update CSS variables in the admin inline stylesheet
function updateAdminStyleVariable(property, value) {
	const styleElement = document.getElementById('rewardswp-admin-styles-inline-css');
	if (!styleElement?.sheet) return;

	for (const rule of styleElement.sheet.cssRules) {
		if (rule.selectorText?.includes('rewards-panel')) {
			rule.style.setProperty(property, value);
			return;
		}
	}
}

// Handle CSS variable updates for admin preview
window.addEventListener('setting-updated', (event) => {
	if (event.detail.key === 'launcher_position') {
		const isLeft = event.detail.value === 'left';
		updateAdminStyleVariable('--rwp-panel-left', isLeft ? '24px' : 'auto');
		updateAdminStyleVariable('--rwp-panel-right', isLeft ? 'auto' : '24px');
		updateAdminStyleVariable('--rwp-launcher-left', isLeft ? '24px' : 'auto');
		updateAdminStyleVariable('--rwp-launcher-right', isLeft ? 'auto' : '24px');
		updateAdminStyleVariable('--rwp-panel-transform-origin', isLeft ? 'bottom left' : 'bottom right');
	}
	if (event.detail.key === 'brand_color') {
		const value = event.detail.value;
		updateAdminStyleVariable('--color-rwp-brand', value);
		updateAdminStyleVariable('--color-rwp-brand-hover', `color-mix(in srgb, white 10%, ${value})`);
	}
	if (event.detail.key === 'brand_foreground_color') {
		const value = event.detail.value;
		updateAdminStyleVariable('--color-rwp-widget-header-text', value);
		updateAdminStyleVariable('--color-rwp-widget-button-text', value);
	}
});
