/**
 * Shortcode Generator — Admin UI logic.
 *
 * Handles:
 *  - Real-time shortcode string generation from form controls
 *  - Debounced AJAX preview of rendered widget
 *  - Copy-to-clipboard for generated shortcode
 *  - Conditional accent control visibility
 *
 * @since 0.1.0
 */
(function () {
	'use strict';

	const GENERATOR_CONFIG = window.crtodayShortcodeGeneratorConfig || {};
	const CTA_BUTTON_TEXT_DEFAULT = (GENERATOR_CONFIG.defaults || {}).ctaButtonText || '';
	const CTA_BUTTON_TEXT_EXCHANGE = (GENERATOR_CONFIG.defaults || {}).ctaButtonTextExchange || '';

	/* ================================================================
	 * Constants
	 * ================================================================ */

	const SHARED_WIDGET_DEFAULTS = {
		amount: '1',
		decimals: '2',
		theme: 'light',
		radius: '12',
		shadow: true,
		seamless: false,
		showFlags: true,
		attribution: true,
		compactMode: true,
		fullWidth: false,
		sourceLabel: '',
		showTimestamp: true,
		adjustmentPercent: '0',
		showCtaButton: false,
		ctaButtonText: CTA_BUTTON_TEXT_DEFAULT,
		ctaUrlTemplate: '',
		enableStructuredData: false,
		accentMode: 'none',
		accentTheme: 'default',
		accentAngle: '135',
	};

	const DEFAULTS = {
		source: 'FEX:STANDARD',
		base: 'USD',
		symbols: 'EUR,GBP,CHF,JPY',
		invertRates: false,
		currencyLabelMode: 'code',
		showCurrencySymbols: false,
		showChange: false,
		...SHARED_WIDGET_DEFAULTS,
		ctaButtonText: CTA_BUTTON_TEXT_EXCHANGE,
	};

	const CONVERTER_DEFAULTS = {
		source: 'FEX:STANDARD',
		from: 'USD',
		to: 'EUR',
		...SHARED_WIDGET_DEFAULTS,
	};

	const TICKER_DEFAULTS = {
		source: 'FEX:STANDARD',
		pairs: 'EUR/USD,GBP/USD,USD/JPY,EUR/JPY,GBP/JPY',
		amount: '1',
		adjustmentPercent: '0',
		decimals: '2',
		theme: 'light',
		currencyLabelMode: 'code',
		speed: '40',
		direction: 'left',
		separator: 'dot',
		showFlags: true,
		showChange: true,
		showTimestamp: true,
		pauseOnHover: true,
		attribution: true,
		compactMode: true,
		sourceLabel: '',
		borderX: false,
		borderY: true,
	};

	const RATE_CARD_DEFAULTS = {
		source: 'FEX:STANDARD',
		from: 'EUR',
		to: 'USD',
		amount: '1',
		adjustmentPercent: '0',
		decimals: '4',
		theme: 'light',
		radius: '12',
		shadow: true,
		seamless: false,
		showFlags: true,
		showChange: true,
		currencyLabelMode: 'code',
		showTimestamp: true,
		attribution: true,
		compactMode: true,
		fullWidth: false,
		showChart: true,
		accentMode: 'none',
		accentTheme: 'default',
		accentAngle: '135',
		sourceLabel: '',
	};

	const PRICE_BADGE_DEFAULTS = {
		source: 'FEX:STANDARD',
		primaryCurrency: 'USD',
		primaryPrice: '100',
		targetCurrencies: 'EUR,GBP,JPY',
		decimals: '2',
		theme: 'light',
		accentTheme: 'default',
		accentMode: 'none',
		accentAngle: '135',
		showFlags: true,
		alwaysExpanded: false,
		showTimestamp: true,
		adjustmentPercent: '0',
		showCurrencySymbols: true,
		sourceLabel: '',
		shadow: true,
		seamless: false,
		radius: '12',
		compactMode: true,
		attribution: true,
		fullWidth: false,
	};

	const DEBOUNCE_MS = 600;

	/* ================================================================
	 * State
	 * ================================================================ */

	let previewTimer = null;
	let currentXHR = null;
	let converterPreviewTimer = null;
	let converterCurrentXHR = null;
	let tickerPreviewTimer = null;
	let tickerCurrentXHR = null;
	let rateCardPreviewTimer = null;
	let rateCardCurrentXHR = null;
	let priceBadgePreviewTimer = null;
	let priceBadgeCurrentXHR = null;

	/* ================================================================
	 * Init
	 * ================================================================ */

	function init() {
		initFolderTabs();
		initExchangeGenerator();
		initConverterGenerator();
		initTickerGenerator();
		initRateCardGenerator();
		initPriceBadgeGenerator();
		initAccentThemeSwatches();
	}

	/* ================================================================
	 * Folder-style tab navigation for shortcode generators
	 * ================================================================ */

	function initFolderTabs() {
		var navs = document.querySelectorAll('.crtoday-sc-nav');
		navs.forEach(function (nav) {
			nav.addEventListener('click', function (e) {
				var btn = e.target.closest('.crtoday-sc-nav-btn');
				if (!btn) return;

				var paneId = btn.getAttribute('data-pane');
				if (!paneId) return;

				/* Update tab buttons */
				nav.querySelectorAll('.crtoday-sc-nav-btn').forEach(function (b) {
					b.classList.remove('is-active');
					b.setAttribute('aria-selected', 'false');
				});
				btn.classList.add('is-active');
				btn.setAttribute('aria-selected', 'true');

				/* Switch panes */
				var folder = nav.closest('.crtoday-sc-folder');
				folder.querySelectorAll('.crtoday-sc-pane').forEach(function (p) {
					p.classList.remove('is-active');
				});
				var target = folder.querySelector('.crtoday-sc-pane[data-pane="' + paneId + '"]');
				if (target) target.classList.add('is-active');
			});
		});
	}

	function initExchangeGenerator() {
		const form = document.getElementById('crtoday-shortcode-form');
		if (!form) return;

		initSettingsTabs(form);

		/* Bind all controls */
		form.addEventListener('input', handleChange);
		form.addEventListener('change', handleChange);

		/* Range sliders — live value display */
		bindRangeDisplay('crtoday-sc-decimals', 'crtoday-decimals-value', '');
		bindRangeDisplay('crtoday-sc-radius', 'crtoday-radius-value', 'px');
		bindRangeDisplay('crtoday-sc-accentAngle', 'crtoday-accent-angle-value', '°');

		/* Tag-based currency input */
		initTagInput();

		/* Copy button */
		const copyBtn = document.getElementById('crtoday-copy-shortcode');
		if (copyBtn) {
			copyBtn.addEventListener('click', handleCopy);
		}

		/* Initial build */
		updateShortcodeOutput();
		requestPreview();
		updateAccentVisibility();
		updateCtaControlsState();
		updateSeamlessControlsState();
	}

	function initConverterGenerator() {
		const form = document.getElementById('crtoday-conv-shortcode-form');
		if (!form) return;

		initSettingsTabs(form);

		form.addEventListener('input', handleConverterChange);
		form.addEventListener('change', handleConverterChange);

		bindRangeDisplay('crtoday-conv-sc-decimals', 'crtoday-conv-decimals-value', '');
		bindRangeDisplay('crtoday-conv-sc-radius', 'crtoday-conv-radius-value', 'px');
		bindRangeDisplay('crtoday-conv-sc-accentAngle', 'crtoday-conv-accent-angle-value', '°');

		const copyBtn = document.getElementById('crtoday-conv-copy-shortcode');
		if (copyBtn) {
			copyBtn.addEventListener('click', handleConverterCopy);
		}

		updateConverterShortcodeOutput();
		requestConverterPreview();
		updateConverterAccentVisibility();
		updateConverterCtaControlsState();
		updateConverterSeamlessControlsState();
	}

	function initSettingsTabs(form) {
		const tabs = form.querySelector('[data-crtoday-settings-tabs]');
		if (!tabs) return;

		const buttons = Array.from(tabs.querySelectorAll('[data-crtoday-tab]'));
		const panes = Array.from(form.querySelectorAll('[data-crtoday-pane]'));
		if (buttons.length === 0 || panes.length === 0) return;

		function setActive(tabName) {
			buttons.forEach(function (button) {
				const isActive = button.getAttribute('data-crtoday-tab') === tabName;
				button.classList.toggle('button-primary', isActive);
				button.classList.toggle('button-secondary', !isActive);
			});

			panes.forEach(function (pane) {
				var isActive = pane.getAttribute('data-crtoday-pane') === tabName;
				pane.classList.toggle('is-hidden', !isActive);
				pane.style.display = isActive ? '' : 'none';
			});
		}

		buttons.forEach(function (button) {
			button.addEventListener('click', function () {
				setActive(button.getAttribute('data-crtoday-tab'));
			});
		});

		setActive(buttons[0].getAttribute('data-crtoday-tab'));
	}

	function bindRangeDisplay(inputId, displayId, suffix) {
		var input = document.getElementById(inputId);
		var display = document.getElementById(displayId);
		if (input && display) {
			input.addEventListener('input', function () {
				display.textContent = input.value + suffix;
			});
		}
	}

	function initAccentThemeSwatches() {
		document.querySelectorAll('[data-crtoday-theme-grid]').forEach(function (grid) {
			var targetSelector = grid.getAttribute('data-crtoday-theme-target');
			if (!targetSelector) return;

			var input = document.querySelector(targetSelector);
			if (!input) return;

			var buttons = Array.from(grid.querySelectorAll('[data-crtoday-theme-value]'));

			function setActive(value) {
				buttons.forEach(function (btn) {
					var isActive = btn.getAttribute('data-crtoday-theme-value') === value;
					btn.classList.toggle('is-active', isActive);
				});
			}

			setActive(input.value);

			buttons.forEach(function (btn) {
				btn.addEventListener('click', function () {
					var nextValue = btn.getAttribute('data-crtoday-theme-value');
					if (!nextValue) return;

					input.value = nextValue;
					setActive(nextValue);
					input.dispatchEvent(new Event('input', { bubbles: true }));
					input.dispatchEvent(new Event('change', { bubbles: true }));
				});
			});
		});
	}

	/* ================================================================
	 * Tag-based currency input (legacy - now handled by currency-picker.js)
	 * ================================================================ */

	function initTagInput() {
		/* Multi-picker is now handled by currency-picker.js.
		 * Events bubble up to form and trigger handleChange(). */
	}

	/* ================================================================
	 * Event handlers
	 * ================================================================ */

	function handleChange() {
		updateShortcodeOutput();
		updateAccentVisibility();
		updateCtaControlsState();
		updateSeamlessControlsState();
		debouncePreview();
	}

	function handleConverterChange() {
		updateConverterShortcodeOutput();
		updateConverterAccentVisibility();
		updateConverterCtaControlsState();
		updateConverterSeamlessControlsState();
		debounceConverterPreview();
	}

	function copyFromOutput(outputId, buttonId) {
		const output = document.getElementById(outputId);
		if (!output) return;

		const text = output.textContent || output.innerText;
		if (navigator.clipboard && navigator.clipboard.writeText) {
			navigator.clipboard.writeText(text).then(function () {
				flashCopyFeedback(buttonId);
			});
			return;
		}

		const ta = document.createElement('textarea');
		ta.value = text;
		ta.style.position = 'fixed';
		ta.style.opacity = '0';
		document.body.appendChild(ta);
		ta.select();
		document.execCommand('copy');
		document.body.removeChild(ta);
		flashCopyFeedback(buttonId);
	}

	function handleCopy() {
		copyFromOutput('crtoday-shortcode-output', 'crtoday-copy-shortcode');
	}

	function handleConverterCopy() {
		copyFromOutput('crtoday-conv-shortcode-output', 'crtoday-conv-copy-shortcode');
	}

	function flashCopyFeedback(buttonId) {
		const btn = document.getElementById(buttonId);
		if (!btn) return;
		const orig = btn.textContent;
		btn.textContent = btn.getAttribute('data-copied-label') || orig;
		btn.disabled = true;
		setTimeout(function () {
			btn.textContent = orig;
			btn.disabled = false;
		}, 1500);
	}

	/* ================================================================
	 * Shortcode string generation
	 * ================================================================ */

	function gatherAttributesFromForm(formId, dataAttr) {
		const form = document.getElementById(formId);
		if (!form) return {};

		const attrs = {};
		form.querySelectorAll('[' + dataAttr + ']').forEach(function (el) {
			const key = el.getAttribute(dataAttr);
			if (!key) return;
			if (el.type === 'checkbox') {
				attrs[key] = el.checked;
			} else {
				attrs[key] = el.value;
			}
		});
		return attrs;
	}

	function gatherAttributes() {
		return gatherAttributesFromForm('crtoday-shortcode-form', 'data-attr');
	}

	function buildShortcodeForTag(attrs, defaults, shortcodeTag) {
		let parts = ['[' + shortcodeTag];

		for (const [key, val] of Object.entries(attrs)) {
			const def = defaults[key];

			/* Skip attributes that match defaults */
			if (def !== undefined) {
				if (typeof def === 'boolean' && val === def) continue;
				if (typeof def !== 'boolean' && String(val) === String(def)) continue;
			}

			/* Format value */
			if (typeof val === 'boolean') {
				parts.push(key + '="' + (val ? 'true' : 'false') + '"');
			} else {
				parts.push(key + '="' + String(val).replace(/"/g, '\\"') + '"');
			}
		}

		parts.push(']');
		return parts.join(' ');
	}

	function buildShortcode(attrs) {
		return buildShortcodeForTag(attrs, DEFAULTS, 'crtoday_exchange_rates');
	}

	function updateShortcodeOutput() {
		const attrs = gatherAttributes();
		const code = buildShortcode(attrs);
		const output = document.getElementById('crtoday-shortcode-output');
		if (output) {
			output.textContent = code;
		}
	}

	function gatherConverterAttributes() {
		return gatherAttributesFromForm('crtoday-conv-shortcode-form', 'data-conv-attr');
	}

	function buildConverterShortcode(attrs) {
		return buildShortcodeForTag(attrs, CONVERTER_DEFAULTS, 'crtoday_currency_converter');
	}

	function updateConverterShortcodeOutput() {
		const attrs = gatherConverterAttributes();
		const code = buildConverterShortcode(attrs);
		const output = document.getElementById('crtoday-conv-shortcode-output');
		if (output) {
			output.textContent = code;
		}
	}

	/* ================================================================
	 * Accent visibility
	 * ================================================================ */



	function updateAccentVisibility() {
		const mode = document.querySelector('[data-attr="accentMode"]');
		if (!mode) return;

		const val = mode.value;
		const angleRow = document.getElementById('crtoday-row-accentAngle');

		if (angleRow) {
			angleRow.style.display = (val === 'stripe') ? '' : 'none';
		}
	}

	function updateConverterAccentVisibility() {
		const form = document.getElementById('crtoday-conv-shortcode-form');
		if (!form) return;

		const mode = form.querySelector('[data-conv-attr="accentMode"]');
		if (!mode) return;

		const val = mode.value;
		const angleRow = document.getElementById('crtoday-conv-row-accentAngle');

		if (angleRow) {
			angleRow.style.display = (val === 'stripe') ? '' : 'none';
		}
	}

	function updateCtaControlsState() {
		const form = document.getElementById('crtoday-shortcode-form');
		if (!form) return;

		const enabledEl = form.querySelector('[data-attr="showCtaButton"]');
		const textEl = form.querySelector('[data-attr="ctaButtonText"]');
		const urlEl = form.querySelector('[data-attr="ctaUrlTemplate"]');
		if (!enabledEl || !textEl || !urlEl) return;

		const enabled = enabledEl.checked;
		textEl.disabled = !enabled;
		urlEl.disabled = !enabled;
	}

	function updateConverterCtaControlsState() {
		const form = document.getElementById('crtoday-conv-shortcode-form');
		if (!form) return;

		const enabledEl = form.querySelector('[data-conv-attr="showCtaButton"]');
		const textEl = form.querySelector('[data-conv-attr="ctaButtonText"]');
		const urlEl = form.querySelector('[data-conv-attr="ctaUrlTemplate"]');
		if (!enabledEl || !textEl || !urlEl) return;

		const enabled = enabledEl.checked;
		textEl.disabled = !enabled;
		urlEl.disabled = !enabled;
	}

	/* ================================================================
	 * Seamless mode — disable visual controls
	 * ================================================================ */

	function updateSeamlessControlsState() {
		const form = document.getElementById('crtoday-shortcode-form');
		if (!form) return;

		const seamlessEl = form.querySelector('[data-attr="seamless"]');
		if (!seamlessEl) return;

		const isSeamless = seamlessEl.checked;

		/* Disable theme, shadow, radius controls */
		const themeEl = form.querySelector('[data-attr="theme"]');
		const shadowEl = form.querySelector('[data-attr="shadow"]');
		const radiusEl = form.querySelector('[data-attr="radius"]');
		if (themeEl) themeEl.disabled = isSeamless;
		if (shadowEl) shadowEl.disabled = isSeamless;
		if (radiusEl) radiusEl.disabled = isSeamless;

		/* Disable all style controls when seamless is enabled */
		const accentModeEl = form.querySelector('[data-attr="accentMode"]');
		const accentThemeEl = form.querySelector('[data-attr="accentTheme"]');
		const accentAngleEl = form.querySelector('[data-attr="accentAngle"]');
		if (accentModeEl) accentModeEl.disabled = isSeamless;
		if (accentThemeEl) accentThemeEl.disabled = isSeamless;
		if (accentAngleEl) accentAngleEl.disabled = isSeamless;

		form
			.querySelectorAll('[data-crtoday-theme-target="#crtoday-sc-accentTheme"] [data-crtoday-theme-value]')
			.forEach(function (button) {
				button.disabled = isSeamless;
			});
	}

	function updateConverterSeamlessControlsState() {
		const form = document.getElementById('crtoday-conv-shortcode-form');
		if (!form) return;

		const seamlessEl = form.querySelector('[data-conv-attr="seamless"]');
		if (!seamlessEl) return;

		const isSeamless = seamlessEl.checked;

		/* Disable theme, shadow, radius controls */
		const themeEl = form.querySelector('[data-conv-attr="theme"]');
		const shadowEl = form.querySelector('[data-conv-attr="shadow"]');
		const radiusEl = form.querySelector('[data-conv-attr="radius"]');
		if (themeEl) themeEl.disabled = isSeamless;
		if (shadowEl) shadowEl.disabled = isSeamless;
		if (radiusEl) radiusEl.disabled = isSeamless;

		/* Disable all style controls when seamless is enabled */
		const accentModeEl = form.querySelector('[data-conv-attr="accentMode"]');
		const accentThemeEl = form.querySelector('[data-conv-attr="accentTheme"]');
		const accentAngleEl = form.querySelector('[data-conv-attr="accentAngle"]');
		if (accentModeEl) accentModeEl.disabled = isSeamless;
		if (accentThemeEl) accentThemeEl.disabled = isSeamless;
		if (accentAngleEl) accentAngleEl.disabled = isSeamless;

		form
			.querySelectorAll('[data-crtoday-theme-target="#crtoday-conv-sc-accentTheme"] [data-crtoday-theme-value]')
			.forEach(function (button) {
				button.disabled = isSeamless;
			});
	}

	/* ================================================================
	 * AJAX Preview
	 * ================================================================ */

	function debouncePreview() {
		if (previewTimer) clearTimeout(previewTimer);
		previewTimer = setTimeout(requestPreview, DEBOUNCE_MS);
	}

	function debounceConverterPreview() {
		if (converterPreviewTimer) clearTimeout(converterPreviewTimer);
		converterPreviewTimer = setTimeout(requestConverterPreview, DEBOUNCE_MS);
	}

	function requestPreviewByConfig(config) {
		const container = document.getElementById(config.containerId);
		if (!container) return;

		const attrs = config.gatherAttributes();
		const nonce = document.getElementById(config.nonceId);
		if (!nonce) return;

		if (config.getXhr()) {
			config.getXhr().abort();
		}

		container.classList.add('crtoday-preview-loading');

		const formData = new FormData();
		formData.append('action', config.action);
		formData.append('nonce', nonce.value);

		for (const [key, val] of Object.entries(attrs)) {
			formData.append('attributes[' + key + ']', typeof val === 'boolean' ? (val ? '1' : '0') : String(val));
		}

		const xhr = new XMLHttpRequest();
		config.setXhr(xhr);
		xhr.open('POST', window.ajaxurl, true);
		xhr.onreadystatechange = function () {
			if (xhr.readyState !== 4) return;

			container.classList.remove('crtoday-preview-loading');

			if (xhr.status === 200) {
				try {
					const resp = JSON.parse(xhr.responseText);
					if (resp.success && resp.data && resp.data.html) {
						container.innerHTML = resp.data.html;
					}
				} catch (_error) {
					/* Ignore parse errors */
				}
			}
			config.setXhr(null);
		};
		xhr.send(formData);
	}

	function requestPreview() {
		requestPreviewByConfig({
			containerId: 'crtoday-shortcode-preview',
			nonceId: 'crtoday-preview-nonce',
			action: 'crtoday_shortcode_preview',
			gatherAttributes: gatherAttributes,
			getXhr: function () { return currentXHR; },
			setXhr: function (xhr) { currentXHR = xhr; },
		});
	}

	function requestConverterPreview() {
		requestPreviewByConfig({
			containerId: 'crtoday-conv-shortcode-preview',
			nonceId: 'crtoday-conv-preview-nonce',
			action: 'crtoday_currency_converter_shortcode_preview',
			gatherAttributes: gatherConverterAttributes,
			getXhr: function () { return converterCurrentXHR; },
			setXhr: function (xhr) { converterCurrentXHR = xhr; },
		});
	}

	/* ================================================================
	 * Ticker generator
	 * ================================================================ */

	function initTickerGenerator() {
		const form = document.getElementById('crtoday-ticker-shortcode-form');
		if (!form) return;

		initSettingsTabs(form);

		form.addEventListener('input', handleTickerChange);
		form.addEventListener('change', handleTickerChange);

		bindRangeDisplay('crtoday-ticker-sc-decimals', 'crtoday-ticker-decimals-value', '');
		bindRangeDisplay('crtoday-ticker-sc-speed', 'crtoday-ticker-speed-value', '');

		initTickerPairsPicker(form);

		const copyBtn = document.getElementById('crtoday-ticker-copy-shortcode');
		if (copyBtn) {
			copyBtn.addEventListener('click', handleTickerCopy);
		}

		updateTickerShortcodeOutput();
		requestTickerPreview();
	}

	function initTickerPairsPicker(form) {
		const wrap = form.querySelector('[data-crtoday-pairs-picker]');
		if (!wrap) return;

		const pairFromInput = document.getElementById('crtoday-ticker-pair-from');
		const pairToInput = document.getElementById('crtoday-ticker-pair-to');
		const addBtn = wrap.querySelector('.crtoday-pair-add-btn');
		const tagsWrap = wrap.querySelector('.crtoday-pairs-picker-tags');
		const fromBtn = wrap.querySelector('.crtoday-pair-from-btn');
		const toBtn = wrap.querySelector('.crtoday-pair-to-btn');

		if (!pairFromInput || !pairToInput || !addBtn || !tagsWrap) return;

		function updateAddBtnState() {
			var fromVal = pairFromInput.value.trim();
			var toVal = pairToInput.value.trim();
			addBtn.disabled = !(fromVal && toVal && fromVal !== toVal);
		}

		pairFromInput.addEventListener('change', updateAddBtnState);
		pairToInput.addEventListener('change', updateAddBtnState);

		addBtn.addEventListener('click', function () {
			var fromCode = pairFromInput.value.trim().toUpperCase();
			var toCode = pairToInput.value.trim().toUpperCase();
			if (!fromCode || !toCode || fromCode === toCode) return;

			var pairStr = fromCode + '/' + toCode;
			var hiddenInput = document.getElementById('crtoday-ticker-sc-pairs');
			if (!hiddenInput) return;

			var currentPairs = hiddenInput.value ? hiddenInput.value.split(',').map(function (s) { return s.trim(); }).filter(Boolean) : [];
			if (currentPairs.indexOf(pairStr) >= 0) return;

			currentPairs.push(pairStr);
			hiddenInput.value = currentPairs.join(',');

			rebuildPairTags(tagsWrap, currentPairs);
			resetPairPickers(fromBtn, toBtn, pairFromInput, pairToInput);
			addBtn.disabled = true;

			hiddenInput.dispatchEvent(new Event('change', { bubbles: true }));
		});

		tagsWrap.addEventListener('click', function (e) {
			var removeBtn = e.target.closest('.crtoday-tag-remove');
			if (!removeBtn) return;

			var tag = removeBtn.closest('.crtoday-pair-tag');
			if (!tag) return;

			var from = tag.dataset.from;
			var to = tag.dataset.to;
			var pairStr = from + '/' + to;
			var hiddenInput = document.getElementById('crtoday-ticker-sc-pairs');
			if (!hiddenInput) return;

			var currentPairs = hiddenInput.value.split(',').map(function (s) { return s.trim(); }).filter(Boolean);
			var idx = currentPairs.indexOf(pairStr);
			if (idx >= 0) currentPairs.splice(idx, 1);

			hiddenInput.value = currentPairs.join(',');
			rebuildPairTags(tagsWrap, currentPairs);

			hiddenInput.dispatchEvent(new Event('change', { bubbles: true }));
		});
	}

	function rebuildPairTags(tagsWrap, pairs) {
		var flagBaseUrl = window.crtodayFlagBaseUrl || '';
		var currencies = window.crtodayAdminCurrencies || [];

		tagsWrap.innerHTML = '';

		pairs.forEach(function (pairStr) {
			var parts = pairStr.split('/');
			if (parts.length !== 2) return;

			var fromCode = parts[0];
			var toCode = parts[1];

			var fromInfo = currencies.find(function (c) { return c.code === fromCode; });
			var toInfo = currencies.find(function (c) { return c.code === toCode; });

			var tag = document.createElement('span');
			tag.className = 'crtoday-tag crtoday-pair-tag';
			tag.dataset.from = fromCode;
			tag.dataset.to = toCode;

			if (fromInfo && fromInfo.flag && flagBaseUrl) {
				var fromImg = document.createElement('img');
				fromImg.className = 'crtoday-tag-flag';
				fromImg.src = flagBaseUrl + fromInfo.flag + '.webp';
				fromImg.alt = '';
				fromImg.width = 16;
				fromImg.height = 16;
				tag.appendChild(fromImg);
			}

			var codeSpan = document.createElement('span');
			codeSpan.textContent = fromCode + '/' + toCode;
			tag.appendChild(codeSpan);

			if (toInfo && toInfo.flag && flagBaseUrl) {
				var toImg = document.createElement('img');
				toImg.className = 'crtoday-tag-flag';
				toImg.src = flagBaseUrl + toInfo.flag + '.webp';
				toImg.alt = '';
				toImg.width = 16;
				toImg.height = 16;
				tag.appendChild(toImg);
			}

			var removeBtn = document.createElement('button');
			removeBtn.type = 'button';
			removeBtn.className = 'crtoday-tag-remove';
			removeBtn.setAttribute('aria-label', 'Remove ' + pairStr);
			removeBtn.innerHTML = '×';
			tag.appendChild(removeBtn);

			tagsWrap.appendChild(tag);
		});
	}

	function resetPairPickers(fromBtn, toBtn, fromInput, toInput) {
		fromInput.value = '';
		toInput.value = '';

		if (fromBtn) {
			fromBtn.dataset.value = '';
			var fromCode = fromBtn.querySelector('.crtoday-picker-code');
			var fromFlag = fromBtn.querySelector('.crtoday-picker-flag');
			if (fromCode) fromCode.textContent = 'From';
			if (fromFlag) fromFlag.remove();
		}

		if (toBtn) {
			toBtn.dataset.value = '';
			var toCode = toBtn.querySelector('.crtoday-picker-code');
			var toFlag = toBtn.querySelector('.crtoday-picker-flag');
			if (toCode) toCode.textContent = 'To';
			if (toFlag) toFlag.remove();
		}
	}

	function handleTickerChange() {
		updateTickerShortcodeOutput();
		debounceTickerPreview();
	}

	function handleTickerCopy() {
		copyFromOutput('crtoday-ticker-shortcode-output', 'crtoday-ticker-copy-shortcode');
	}

	function gatherTickerAttributes() {
		return gatherAttributesFromForm('crtoday-ticker-shortcode-form', 'data-ticker-attr');
	}

	function updateTickerShortcodeOutput() {
		var attrs = gatherTickerAttributes();
		var code = buildShortcodeForTag(attrs, TICKER_DEFAULTS, 'crtoday_ticker');
		var output = document.getElementById('crtoday-ticker-shortcode-output');
		if (output) {
			output.textContent = code;
		}
	}

	function debounceTickerPreview() {
		if (tickerPreviewTimer) clearTimeout(tickerPreviewTimer);
		tickerPreviewTimer = setTimeout(requestTickerPreview, DEBOUNCE_MS);
	}

	function requestTickerPreview() {
		requestPreviewByConfig({
			containerId: 'crtoday-ticker-shortcode-preview',
			nonceId: 'crtoday-ticker-preview-nonce',
			action: 'crtoday_ticker_shortcode_preview',
			gatherAttributes: gatherTickerAttributes,
			getXhr: function () { return tickerCurrentXHR; },
			setXhr: function (xhr) { tickerCurrentXHR = xhr; },
		});
	}

	/* ================================================================
	 * Rate Card generator
	 * ================================================================ */

	function initRateCardGenerator() {
		const form = document.getElementById('crtoday-rc-shortcode-form');
		if (!form) return;

		initSettingsTabs(form);

		form.addEventListener('input', handleRateCardChange);
		form.addEventListener('change', handleRateCardChange);

		bindRangeDisplay('crtoday-rc-sc-decimals', 'crtoday-rc-decimals-value', '');
		bindRangeDisplay('crtoday-rc-sc-radius', 'crtoday-rc-radius-value', 'px');
		bindRangeDisplay('crtoday-rc-sc-accentAngle', 'crtoday-rc-accent-angle-value', '°');

		const copyBtn = document.getElementById('crtoday-rc-copy-shortcode');
		if (copyBtn) {
			copyBtn.addEventListener('click', handleRateCardCopy);
		}

		updateRateCardShortcodeOutput();
		requestRateCardPreview();
		updateRateCardAccentVisibility();
		updateRateCardSeamlessControlsState();
	}

	function handleRateCardChange() {
		updateRateCardShortcodeOutput();
		updateRateCardAccentVisibility();
		updateRateCardSeamlessControlsState();
		debounceRateCardPreview();
	}

	function handleRateCardCopy() {
		copyFromOutput('crtoday-rc-shortcode-output', 'crtoday-rc-copy-shortcode');
	}

	function gatherRateCardAttributes() {
		return gatherAttributesFromForm('crtoday-rc-shortcode-form', 'data-rc-attr');
	}

	function updateRateCardShortcodeOutput() {
		var attrs = gatherRateCardAttributes();
		var code = buildShortcodeForTag(attrs, RATE_CARD_DEFAULTS, 'crtoday_rate_card');
		var output = document.getElementById('crtoday-rc-shortcode-output');
		if (output) {
			output.textContent = code;
		}
	}

	function updateRateCardAccentVisibility() {
		var form = document.getElementById('crtoday-rc-shortcode-form');
		if (!form) return;

		var mode = form.querySelector('[data-rc-attr="accentMode"]');
		if (!mode) return;

		var val = mode.value;
		var angleRow = document.getElementById('crtoday-rc-row-accentAngle');

		if (angleRow) {
			angleRow.style.display = (val === 'stripe') ? '' : 'none';
		}
	}

	function updateRateCardSeamlessControlsState() {
		var form = document.getElementById('crtoday-rc-shortcode-form');
		if (!form) return;

		var seamlessEl = form.querySelector('[data-rc-attr="seamless"]');
		if (!seamlessEl) return;

		var isSeamless = seamlessEl.checked;

		var themeEl = form.querySelector('[data-rc-attr="theme"]');
		var shadowEl = form.querySelector('[data-rc-attr="shadow"]');
		var radiusEl = form.querySelector('[data-rc-attr="radius"]');
		if (themeEl) themeEl.disabled = isSeamless;
		if (shadowEl) shadowEl.disabled = isSeamless;
		if (radiusEl) radiusEl.disabled = isSeamless;

		var accentModeEl = form.querySelector('[data-rc-attr="accentMode"]');
		var accentThemeEl = form.querySelector('[data-rc-attr="accentTheme"]');
		var accentAngleEl = form.querySelector('[data-rc-attr="accentAngle"]');
		if (accentModeEl) accentModeEl.disabled = isSeamless;
		if (accentThemeEl) accentThemeEl.disabled = isSeamless;
		if (accentAngleEl) accentAngleEl.disabled = isSeamless;

		form
			.querySelectorAll('[data-crtoday-theme-target="#crtoday-rc-sc-accentTheme"] [data-crtoday-theme-value]')
			.forEach(function (button) {
				button.disabled = isSeamless;
			});
	}

	function debounceRateCardPreview() {
		if (rateCardPreviewTimer) clearTimeout(rateCardPreviewTimer);
		rateCardPreviewTimer = setTimeout(requestRateCardPreview, DEBOUNCE_MS);
	}

	function requestRateCardPreview() {
		requestPreviewByConfig({
			containerId: 'crtoday-rc-shortcode-preview',
			nonceId: 'crtoday-rc-preview-nonce',
			action: 'crtoday_rate_card_shortcode_preview',
			gatherAttributes: gatherRateCardAttributes,
			getXhr: function () { return rateCardCurrentXHR; },
			setXhr: function (xhr) { rateCardCurrentXHR = xhr; },
		});
	}

	/* ================================================================
	 * Price Badge generator
	 * ================================================================ */

	function initPriceBadgeGenerator() {
		var form = document.getElementById('crtoday-pb-shortcode-form');
		if (!form) return;

		initSettingsTabs(form);

		form.addEventListener('input', handlePriceBadgeChange);
		form.addEventListener('change', handlePriceBadgeChange);

		bindRangeDisplay('crtoday-pb-sc-decimals', 'crtoday-pb-decimals-value', '');
		bindRangeDisplay('crtoday-pb-sc-radius', 'crtoday-pb-radius-value', 'px');
		bindRangeDisplay('crtoday-pb-sc-accentAngle', 'crtoday-pb-accent-angle-value', '°');

		var copyBtn = document.getElementById('crtoday-pb-copy-shortcode');
		if (copyBtn) {
			copyBtn.addEventListener('click', handlePriceBadgeCopy);
		}

		updatePriceBadgeShortcodeOutput();
		requestPriceBadgePreview();
		updatePriceBadgeSeamlessControlsState();
	}

	function handlePriceBadgeChange() {
		updatePriceBadgeShortcodeOutput();
		updatePriceBadgeSeamlessControlsState();
		debouncePriceBadgePreview();
	}

	function handlePriceBadgeCopy() {
		copyFromOutput('crtoday-pb-shortcode-output', 'crtoday-pb-copy-shortcode');
	}

	function gatherPriceBadgeAttributes() {
		return gatherAttributesFromForm('crtoday-pb-shortcode-form', 'data-pb-attr');
	}

	function updatePriceBadgeShortcodeOutput() {
		var attrs = gatherPriceBadgeAttributes();
		var code = buildShortcodeForTag(attrs, PRICE_BADGE_DEFAULTS, 'crtoday_price_badge');
		var output = document.getElementById('crtoday-pb-shortcode-output');
		if (output) {
			output.textContent = code;
		}
	}

	function debouncePriceBadgePreview() {
		if (priceBadgePreviewTimer) clearTimeout(priceBadgePreviewTimer);
		priceBadgePreviewTimer = setTimeout(requestPriceBadgePreview, DEBOUNCE_MS);
	}

	function requestPriceBadgePreview() {
		requestPreviewByConfig({
			containerId: 'crtoday-pb-shortcode-preview',
			nonceId: 'crtoday-pb-preview-nonce',
			action: 'crtoday_price_badge_shortcode_preview',
			gatherAttributes: gatherPriceBadgeAttributes,
			getXhr: function () { return priceBadgeCurrentXHR; },
			setXhr: function (xhr) { priceBadgeCurrentXHR = xhr; },
		});
	}

	function updatePriceBadgeSeamlessControlsState() {
		var form = document.getElementById('crtoday-pb-shortcode-form');
		if (!form) return;

		var seamlessEl = form.querySelector('[data-pb-attr="seamless"]');
		if (!seamlessEl) return;

		var isSeamless = seamlessEl.checked;

		var themeEl = form.querySelector('[data-pb-attr="theme"]');
		var shadowEl = form.querySelector('[data-pb-attr="shadow"]');
		var radiusEl = form.querySelector('[data-pb-attr="radius"]');
		if (themeEl) themeEl.disabled = isSeamless;
		if (shadowEl) shadowEl.disabled = isSeamless;
		if (radiusEl) radiusEl.disabled = isSeamless;

		var accentModeEl = form.querySelector('[data-pb-attr="accentMode"]');
		var accentThemeEl = form.querySelector('[data-pb-attr="accentTheme"]');
		var accentAngleEl = form.querySelector('[data-pb-attr="accentAngle"]');
		if (accentModeEl) accentModeEl.disabled = isSeamless;
		if (accentThemeEl) accentThemeEl.disabled = isSeamless;
		if (accentAngleEl) accentAngleEl.disabled = isSeamless;

		form
			.querySelectorAll('[data-crtoday-theme-target="#crtoday-pb-sc-accentTheme"] [data-crtoday-theme-value]')
			.forEach(function (button) {
				button.disabled = isSeamless;
			});
	}

	/* ================================================================
	 * Boot
	 * ================================================================ */

	if (document.readyState === 'loading') {
		document.addEventListener('DOMContentLoaded', init);
	} else {
		init();
	}
})();
