/**
 * BaseCloud Boost — Admin JavaScript (v8)
 * Premium dark glassmorphism UI with liquid fuel page transitions.
 * Vanilla JS — no jQuery dependency.
 */
( function () {
	'use strict';

	const cfg   = window.bcbAdmin || {};
	const ajax  = cfg.ajaxUrl || '';
	const nonce = cfg.nonce   || '';
	const str   = cfg.strings || {};

	// ── Utilities ──────────────────────────────────────────────────────────

	function doAjax( action, data ) {
		const body = new URLSearchParams( { action, nonce, ...data } );
		return fetch( ajax, {
			method:  'POST',
			headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
			body:    body.toString(),
		} ).then( function ( res ) {
			if ( ! res.ok ) throw new Error( 'HTTP ' + res.status );
			return res.json();
		} );
	}

	function showFeedback( el, message, success ) {
		if ( ! el ) return;
		el.textContent   = message;
		el.className     = 'bcboost-feedback bcboost-feedback--' + ( success ? 'success' : 'error' );
		el.style.display = 'block';
		setTimeout( function () { el.style.display = 'none'; }, 5000 );
	}

	// ── Liquid Page Transition ────────────────────────────────────────────
	// Fuel-surge effect: green circle expands from the clicked nav item
	// covering the screen, then the new page drains it away from the top.

	var transitionOverlay = null;

	function createTransitionOverlay() {
		if ( transitionOverlay ) return;
		transitionOverlay = document.createElement( 'div' );
		transitionOverlay.id        = 'bcboost-liquid-overlay';
		transitionOverlay.setAttribute( 'aria-hidden', 'true' );
		document.body.appendChild( transitionOverlay );
	}

	function triggerLiquidExit( originX, originY, callback ) {
		if ( ! transitionOverlay ) return;
		transitionOverlay.classList.remove( 'bcboost-liquid--drain' );
		// Force reflow so the removal registers before adding fill class.
		void transitionOverlay.offsetWidth;
		transitionOverlay.classList.add( 'bcboost-liquid--fill' );

		setTimeout( callback, 180 );
	}

	function triggerLiquidEnter() {
		if ( ! transitionOverlay ) return;
		transitionOverlay.classList.remove( 'bcboost-liquid--fill' );
		transitionOverlay.classList.add( 'bcboost-liquid--drain' );
	}

	function initPageTransitions() {
		createTransitionOverlay();

		// Drain away on initial page load — gives arriving pages a fuel-drain reveal.
		requestAnimationFrame( function () {
			requestAnimationFrame( function () {
				triggerLiquidEnter();
			} );
		} );

		// Intercept nav link clicks.
		document.querySelectorAll( '.bcboost-nav-item' ).forEach( function ( link ) {
			link.addEventListener( 'click', function ( e ) {
				// Skip if modifier keys held (open in new tab etc.)
				if ( e.metaKey || e.ctrlKey || e.shiftKey || e.altKey ) return;

				var href = link.getAttribute( 'href' );
				if ( ! href || href === '#' ) return;

				e.preventDefault();

				var rect = link.getBoundingClientRect();
				var ox   = Math.round( rect.left + rect.width  / 2 );
				var oy   = Math.round( rect.top  + rect.height / 2 );

				triggerLiquidExit( ox, oy, function () {
					window.location.href = href;
				} );
			} );
		} );
	}

	// ── Stat Counter Animation ────────────────────────────────────────────

	function initStatCounters() {
		document.querySelectorAll( '.bcboost-stat-value[data-count], .bcboost-ring-value[data-count]' ).forEach( function ( el ) {
			const raw      = el.getAttribute( 'data-count' );
			if ( ! raw ) return;
			const target   = parseFloat( raw );
			const isFloat  = raw.includes( '.' );
			const suffix   = el.getAttribute( 'data-suffix' ) || '';
			const prefix   = el.getAttribute( 'data-prefix' ) || '';
			const duration = 900;
			const steps    = 50;
			let   current  = 0;
			const step     = target / steps;

			const timer = setInterval( function () {
				current = Math.min( current + step, target );
				el.textContent = prefix + ( isFloat ? current.toFixed( 1 ) : Math.floor( current ) ) + suffix;
				if ( current >= target ) {
					clearInterval( timer );
					el.textContent = prefix + ( isFloat ? target.toFixed( 1 ) : target ) + suffix;
				}
			}, duration / steps );
		} );
	}

	// ── Stagger card entrance ─────────────────────────────────────────────

	function initCardStagger() {
		const cards = document.querySelectorAll( '.bcboost-card, .bcboost-stat-card' );
		cards.forEach( function ( card, i ) {
			card.style.opacity   = '0';
			card.style.transform = 'translateY(20px)';
			card.style.transition = 'opacity 0.4s ease, transform 0.4s ease';
			setTimeout( function () {
				card.style.opacity   = '1';
				card.style.transform = 'translateY(0)';
			}, 60 + i * 40 );
		} );
	}

	// ── Ambient particle haze ("Classic" backdrop) ───────────────────────
	// Lightweight 2D canvas effect; only runs for the classic backdrop
	// (dark/light modes render no canvas at all). Particle colours follow
	// the user's chosen accents.

	function hexToRgbString( hex, fallback ) {
		var m = /^#?([0-9a-f]{6})$/i.exec( String( hex || '' ).trim() );
		if ( ! m ) return fallback;
		var n = parseInt( m[ 1 ], 16 );
		return ( ( n >> 16 ) & 255 ) + ',' + ( ( n >> 8 ) & 255 ) + ',' + ( n & 255 );
	}

	function initParticles() {
		if ( ( cfg.theme || 'dark' ) !== 'classic' ) return;
		const canvas = document.getElementById( 'bcboost-ballpit-canvas' );
		if ( ! canvas ) return;

		// Static reduced-motion users get the plain gradient — no drift.
		if ( window.matchMedia && window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches ) return;

		var colA = hexToRgbString( cfg.colors && cfg.colors.primary,   '74,222,128' );
		var colB = hexToRgbString( cfg.colors && cfg.colors.secondary, '139,92,246' );

		canvas.style.position      = 'fixed';
		canvas.style.top           = '0';
		canvas.style.left          = '0';
		canvas.style.width         = '100%';
		canvas.style.height        = '100%';
		canvas.style.zIndex        = '0';
		canvas.style.pointerEvents = 'none';

		const ctx = canvas.getContext( '2d' );
		if ( ! ctx ) return;

		var W = 0, H = 0;
		var particles = [];
		var raf;

		function resize() {
			W = canvas.width  = window.innerWidth;
			H = canvas.height = window.innerHeight;
		}

		function rand( min, max ) { return min + Math.random() * ( max - min ); }

		function createParticle() {
			return {
				x:     rand( 0, W ),
				y:     rand( 0, H ),
				r:     rand( 1, 3.5 ),
				dx:    rand( -0.18, 0.18 ),
				dy:    rand( -0.22, -0.05 ),
				alpha: rand( 0.04, 0.22 ),
				color: Math.random() > 0.6 ? colB : colA,
			};
		}

		function initParticlePool() {
			particles = [];
			var count = Math.min( 80, Math.floor( W * H / 14000 ) );
			for ( var i = 0; i < count; i++ ) {
				particles.push( createParticle() );
			}
		}

		function draw() {
			ctx.clearRect( 0, 0, W, H );
			for ( var i = 0; i < particles.length; i++ ) {
				var p = particles[ i ];
				ctx.beginPath();
				ctx.arc( p.x, p.y, p.r, 0, Math.PI * 2 );
				ctx.fillStyle = 'rgba(' + p.color + ',' + p.alpha + ')';
				ctx.fill();

				p.x += p.dx;
				p.y += p.dy;

				// Respawn when drifted off-screen.
				if ( p.y < -10 || p.x < -10 || p.x > W + 10 ) {
					p.x     = rand( 0, W );
					p.y     = H + 10;
					p.alpha = rand( 0.04, 0.22 );
				}
			}
			raf = requestAnimationFrame( draw );
		}

		window.addEventListener( 'resize', function () {
			resize();
			initParticlePool();
		} );

		resize();
		initParticlePool();
		draw();
	}

	// ── Glowing progress bar ──────────────────────────────────────────────

	function animateProgressBar() {
		var fill = document.getElementById( 'bcboost-progress-fill' );
		if ( ! fill ) return;
		var target = parseInt( fill.style.width, 10 ) || 0;
		fill.style.width = '0%';
		setTimeout( function () {
			fill.style.transition = 'width 0.9s cubic-bezier(0.34,1.56,0.64,1)';
			fill.style.width = target + '%';
		}, 300 );
	}

	// ── Boost button pulse ────────────────────────────────────────────────

	function initBoostButtonPulse() {
		document.querySelectorAll( '.bcboost-btn--primary' ).forEach( function ( btn ) {
			btn.addEventListener( 'mouseenter', function () {
				btn.classList.add( 'bcboost-btn--pulsing' );
			} );
			btn.addEventListener( 'mouseleave', function () {
				btn.classList.remove( 'bcboost-btn--pulsing' );
			} );
		} );
	}

	// ── Clear Cache ───────────────────────────────────────────────────────

	function bindClearCache( selector ) {
		document.querySelectorAll( selector ).forEach( function ( btn ) {
			btn.addEventListener( 'click', function () {
				if ( ! confirm( str.confirmClear || 'Clear all cache?' ) ) return;

				const originalHTML = btn.innerHTML;
				btn.disabled    = true;
				btn.textContent = str.clearing || 'Boosting\u2026';

				doAjax( 'BCBOOST_clear_cache' )
					.then( function ( res ) {
						btn.disabled  = false;
						btn.innerHTML = originalHTML;
						const fb = document.getElementById( 'bcboost-action-feedback' );
						showFeedback( fb, res.data.message, res.success );
					} )
					.catch( function () {
						btn.disabled  = false;
						btn.innerHTML = originalHTML;
					} );
			} );
		} );
	}

	// ── Database Cleanup ──────────────────────────────────────────────────

	function bindDbCleanup() {
		const feedback = document.getElementById( 'bcboost-db-feedback' );

		document.querySelectorAll( '.bcboost-cleanup-btn' ).forEach( function ( btn ) {
			btn.addEventListener( 'click', function () {
				const action = btn.dataset.action;
				if ( ! confirm( str.confirmCleanup || 'Run cleanup?' ) ) return;
				btn.disabled = true;

				doAjax( 'BCBOOST_db_cleanup', { cleanup_action: action } )
					.then( function ( res ) {
						btn.disabled = false;
						const deleted = res.data.deleted || 0;
						const msg     = res.success
							? 'Done. ' + deleted + ' items removed.'
							: str.error;
						showFeedback( feedback, msg, res.success );
					} )
					.catch( function () {
						btn.disabled = false;
						showFeedback( feedback, str.error, false );
					} );
			} );
		} );

		const cleanAll = document.getElementById( 'bcboost-run-all-cleanup' );
		if ( cleanAll ) {
			cleanAll.addEventListener( 'click', function () {
				if ( ! confirm( str.confirmCleanup || 'Run full cleanup?' ) ) return;
				cleanAll.disabled = true;

				doAjax( 'BCBOOST_db_cleanup', { cleanup_action: 'all' } )
					.then( function ( res ) {
						cleanAll.disabled = false;
						if ( res.success ) {
							const total = Object.values( res.data ).reduce( function ( a, b ) {
								return a + ( typeof b === 'number' ? b : 0 );
							}, 0 );
							showFeedback( feedback, 'Full cleanup complete. ' + total + ' items removed.', true );
						} else {
							showFeedback( feedback, str.error, false );
						}
					} )
					.catch( function () {
						cleanAll.disabled = false;
						showFeedback( feedback, str.error, false );
					} );
			} );
		}

		const optimiseBtn = document.getElementById( 'bcboost-optimize-tables' );
		if ( optimiseBtn ) {
			optimiseBtn.addEventListener( 'click', function () {
				optimiseBtn.disabled = true;
				doAjax( 'BCBOOST_db_cleanup', { cleanup_action: 'optimize' } )
					.then( function ( res ) {
						optimiseBtn.disabled = false;
						showFeedback( feedback, 'Tables optimised.', res.success );
					} )
					.catch( function () {
						optimiseBtn.disabled = false;
						showFeedback( feedback, str.error, false );
					} );
			} );
		}

		const dbBtn = document.getElementById( 'bcboost-run-db-cleanup' );
		if ( dbBtn ) {
			dbBtn.addEventListener( 'click', function () {
				if ( ! confirm( str.confirmCleanup || 'Run cleanup?' ) ) return;
				dbBtn.disabled = true;
				doAjax( 'BCBOOST_db_cleanup', { cleanup_action: 'all' } )
					.then( function ( res ) {
						dbBtn.disabled = false;
						const fb = document.getElementById( 'bcboost-action-feedback' );
						showFeedback( fb, res.success ? 'Database cleaned.' : str.error, res.success );
					} )
					.catch( function () { dbBtn.disabled = false; } );
			} );
		}
	}

	// ── Preloader ─────────────────────────────────────────────────────────

	var preloaderPollTimer = null;

	function startPreloaderPolling() {
		if ( preloaderPollTimer ) return;
		preloaderPollTimer = setInterval( updatePreloaderStatus, 5000 );
	}

	function stopPreloaderPolling() {
		if ( preloaderPollTimer ) {
			clearInterval( preloaderPollTimer );
			preloaderPollTimer = null;
		}
	}

	function updatePreloaderStatus() {
		doAjax( 'BCBOOST_preloader_status' )
			.then( function ( res ) {
				if ( ! res.success ) return;
				const d    = res.data;
				const fill = document.getElementById( 'bcboost-progress-fill' );
				const lbl  = document.getElementById( 'bcboost-progress-label' );
				if ( fill ) fill.style.width = d.percentage + '%';
				if ( lbl )  lbl.textContent  = d.completed + ' of ' + d.total + ' pages (' + d.percentage + '%)';
				if ( d.status !== 'running' ) stopPreloaderPolling();
			} )
			.catch( stopPreloaderPolling );
	}

	function bindPreloader() {
		const startBtn   = document.getElementById( 'bcboost-start-preloader' );
		const stopBtn    = document.getElementById( 'bcboost-stop-preloader' );
		const feedbackEl = document.getElementById( 'bcboost-preloader-feedback' );

		if ( startBtn ) {
			startBtn.addEventListener( 'click', function () {
				startBtn.disabled = true;
				doAjax( 'BCBOOST_preloader_start' )
					.then( function ( res ) {
						startBtn.disabled = false;
						showFeedback( feedbackEl, res.data.message, res.success );
						if ( res.success ) startPreloaderPolling();
					} )
					.catch( function () {
						startBtn.disabled = false;
						showFeedback( feedbackEl, str.error, false );
					} );
			} );
		}

		if ( stopBtn ) {
			stopBtn.addEventListener( 'click', function () {
				stopBtn.disabled = true;
				doAjax( 'BCBOOST_preloader_stop' )
					.then( function ( res ) {
						stopBtn.disabled = false;
						stopPreloaderPolling();
						showFeedback( feedbackEl, res.data.message, res.success );
					} )
					.catch( function () { stopBtn.disabled = false; } );
			} );
		}

		if ( document.querySelector( '.bcboost-preloader-status--running' ) ) {
			startPreloaderPolling();
		}
	}

	// ── Webhook ───────────────────────────────────────────────────────────

	function bindWebhook() {
		const feedback = document.getElementById( 'bcboost-webhook-feedback' );
		const testBtn  = document.getElementById( 'bcboost-webhook-test' );
		const sendBtn  = document.getElementById( 'bcboost-send-metrics-now' );
		const urlInput = document.getElementById( 'webhook_url' );

		if ( testBtn ) {
			testBtn.addEventListener( 'click', function () {
				testBtn.disabled    = true;
				testBtn.textContent = 'Testing\u2026';
				const url = urlInput ? urlInput.value : '';
				doAjax( 'BCBOOST_webhook_test', url ? { webhook_url: url } : {} )
					.then( function ( res ) {
						testBtn.disabled    = false;
						testBtn.textContent = 'Test Webhook';
						showFeedback( feedback, res.data.message, res.success );
					} )
					.catch( function () {
						testBtn.disabled    = false;
						testBtn.textContent = 'Test Webhook';
						showFeedback( feedback, str.error, false );
					} );
			} );
		}

		if ( sendBtn ) {
			sendBtn.addEventListener( 'click', function () {
				sendBtn.disabled    = true;
				sendBtn.textContent = 'Sending\u2026';
				doAjax( 'BCBOOST_send_metrics_now' )
					.then( function ( res ) {
						sendBtn.disabled    = false;
						sendBtn.textContent = 'Send Now';
						showFeedback( feedback, res.data.message, res.success );
					} )
					.catch( function () {
						sendBtn.disabled    = false;
						sendBtn.textContent = 'Send Now';
						showFeedback( feedback, str.error, false );
					} );
			} );
		}
	}

	// ── Lighthouse Webhook (settings panel Test/Send Now) ─────────────────

	function bindLighthouseWebhook() {
		var feedback  = document.getElementById( 'bcboost-lh-webhook-feedback' );
		var testBtn   = document.getElementById( 'bcboost-lh-webhook-test' );
		var sendNow   = document.getElementById( 'bcboost-lh-send-now' );
		var urlInput  = document.getElementById( 'lighthouse_webhook_url' );

		if ( testBtn ) {
			testBtn.addEventListener( 'click', function () {
				testBtn.disabled    = true;
				testBtn.textContent = 'Testing\u2026';
				var url = urlInput ? urlInput.value.trim() : '';
				doAjax( 'BCBOOST_lighthouse_webhook_test', url ? { webhook_url: url } : {} )
					.then( function ( res ) {
						testBtn.disabled    = false;
						testBtn.textContent = 'Test Webhook';
						showFeedback( feedback, res.data.message, res.success );
					} )
					.catch( function () {
						testBtn.disabled    = false;
						testBtn.textContent = 'Test Webhook';
						showFeedback( feedback, str.error, false );
					} );
			} );
		}

		if ( sendNow ) {
			sendNow.addEventListener( 'click', function () {
				sendNow.disabled    = true;
				sendNow.textContent = 'Sending\u2026';
				doAjax( 'BCBOOST_lighthouse_send_now' )
					.then( function ( res ) {
						sendNow.disabled    = false;
						sendNow.textContent = 'Send Now';
						showFeedback( feedback, res.data.message, res.success );
					} )
					.catch( function () {
						sendNow.disabled    = false;
						sendNow.textContent = 'Send Now';
						showFeedback( feedback, str.error, false );
					} );
			} );
		}
	}

	// ── Lighthouse ────────────────────────────────────────────────────────

	var lhLastResult = null; // store the last audit result so "Send to Webhook" can use it.

	/**
	 * Derive a score colour class: green ≥90, yellow ≥50, red <50.
	 */
	function lhScoreClass( score ) {
		if ( score >= 90 ) return '';               // green (default ring colour)
		if ( score >= 50 ) return 'bcboost-lh-ring-fill--warn';
		return 'bcboost-lh-ring-fill--fail';
	}

	/**
	 * Animate an SVG ring to the given 0-100 score.
	 */
	function animateLhRing( category, score ) {
		var card   = document.querySelector( '.bcboost-lh-score-ring[data-category="' + category + '"]' );
		if ( ! card ) return;
		var ring   = card.querySelector( '.bcboost-lh-ring-fill' );
		var label  = card.querySelector( '.bcboost-lh-ring-text' );
		var circum = 314; // 2 * pi * r(50)

		ring.setAttribute( 'class', 'bcboost-lh-ring-fill ' + lhScoreClass( score ) );
		ring.style.strokeDashoffset = circum - ( circum * score / 100 );
		if ( label ) label.textContent = score;
	}

	/**
	 * Populate the metrics row from a raw PSI audits object.
	 */
	function populateLhMetrics( audits ) {
		function ms( id ) {
			var a = audits[ id ];
			return a ? Math.round( a.numericValue || 0 ) : 0;
		}
		function flt( id ) {
			var a = audits[ id ];
			return a ? ( a.numericValue || 0 ) : 0;
		}

		var fcp  = ms( 'first-contentful-paint' );
		var lcp  = ms( 'largest-contentful-paint' );
		var tbt  = ms( 'total-blocking-time' );
		var cls  = flt( 'cumulative-layout-shift' );
		var si   = ms( 'speed-index' );
		var tti  = ms( 'interactive' );
		var ttfb = ms( 'server-response-time' );

		var set = function ( id, val ) {
			var el = document.getElementById( id );
			if ( el ) el.textContent = val;
		};

		set( 'bcboost-lh-fcp',  fcp  + ' ms' );
		set( 'bcboost-lh-lcp',  lcp  + ' ms' );
		set( 'bcboost-lh-tbt',  tbt  + ' ms' );
		set( 'bcboost-lh-cls',  cls.toFixed( 3 ) );
		set( 'bcboost-lh-si',   si   + ' ms' );
		set( 'bcboost-lh-tti',  tti  + ' ms' );
		set( 'bcboost-lh-ttfb', ttfb + ' ms' );

		return { fcp_ms: fcp, lcp_ms: lcp, tbt_ms: tbt, cls: cls, speed_index_ms: si, tti_ms: tti, ttfb_ms: ttfb };
	}

	/**
	 * Populate the metrics row from a server-normalised metrics object.
	 */
	function populateLhMetricsNormalized( m ) {
		var set = function ( id, val ) {
			var el = document.getElementById( id );
			if ( el ) el.textContent = val;
		};
		set( 'bcboost-lh-fcp',  ( m.fcp_ms         || 0 ) + ' ms' );
		set( 'bcboost-lh-lcp',  ( m.lcp_ms         || 0 ) + ' ms' );
		set( 'bcboost-lh-tbt',  ( m.tbt_ms         || 0 ) + ' ms' );
		set( 'bcboost-lh-cls',  ( m.cls            || 0 ).toFixed( 3 ) );
		set( 'bcboost-lh-si',   ( m.speed_index_ms || 0 ) + ' ms' );
		set( 'bcboost-lh-tti',  ( m.tti_ms         || 0 ) + ' ms' );
		set( 'bcboost-lh-ttfb', ( m.ttfb_ms        || 0 ) + ' ms' );
	}

	/**
	 * Run a PageSpeed Insights audit (Lighthouse engine) client-side.
	 */
	function bindLighthouse() {
		var runBtn     = document.getElementById( 'bcboost-lh-run' );
		var sendBtn    = document.getElementById( 'bcboost-lh-send' );
		var urlInput   = document.getElementById( 'bcboost-lh-url' );
		var stratSel   = document.getElementById( 'bcboost-lh-strategy' );
		var spinner    = document.getElementById( 'bcboost-lh-spinner' );
		var results    = document.getElementById( 'bcboost-lh-results' );
		var feedback   = document.getElementById( 'bcboost-lh-feedback' );
		var preview    = document.querySelector( '#bcboost-lh-payload-preview code' );

		if ( ! runBtn ) return;

		runBtn.addEventListener( 'click', function () {
			var url      = ( urlInput ? urlInput.value.trim() : '' ) || cfg.siteUrl || '';
			var strategy = stratSel ? stratSel.value : 'mobile';

			if ( ! url ) {
				showFeedback( feedback, 'Please enter a URL to audit.', false );
				return;
			}

			runBtn.disabled  = true;
			if ( sendBtn ) sendBtn.disabled = true;
			if ( spinner )  spinner.style.display  = 'inline-flex';
			if ( results )  results.style.display  = 'none';
			lhLastResult = null;

			// Run the audit server-side so the PSI API key never reaches the browser.
			doAjax( 'BCBOOST_run_psi', { url: url, strategy: strategy } )
				.then( function ( res ) {
					runBtn.disabled = false;
					if ( spinner ) spinner.style.display = 'none';

					if ( ! res.success ) {
						showFeedback( feedback, ( res.data && res.data.message ) || str.error, false );
						return;
					}

					var d       = res.data;
					var scores  = d.scores  || {};
					var metrics = d.metrics || {};

					populateLhMetricsNormalized( metrics );

					animateLhRing( 'performance',    scores.performance    || 0 );
					animateLhRing( 'accessibility',  scores.accessibility  || 0 );
					animateLhRing( 'best-practices', scores.best_practices || 0 );
					animateLhRing( 'seo',            scores.seo            || 0 );

					if ( results ) results.style.display = 'block';

					lhLastResult = {
						tested_url: d.tested_url || url,
						strategy:   d.strategy   || strategy,
						scores:     scores,
						metrics:    metrics,
					};

					if ( preview ) {
						preview.textContent = JSON.stringify( lhLastResult, null, 2 );
					}

					if ( sendBtn ) sendBtn.disabled = false;
					showFeedback( feedback, 'Audit complete! Performance: ' + ( scores.performance || 0 ) + '/100', true );
				} )
				.catch( function ( err ) {
					runBtn.disabled = false;
					if ( spinner ) spinner.style.display = 'none';
					showFeedback( feedback, 'Audit failed: ' + err.message, false );
				} );
		} );

		// Send last result to the Lighthouse webhook via PHP AJAX.
		if ( sendBtn ) {
			sendBtn.addEventListener( 'click', function () {
				if ( ! lhLastResult ) return;
				sendBtn.disabled    = true;
				sendBtn.textContent = 'Sending\u2026';

				doAjax( 'BCBOOST_lighthouse_send', { result: JSON.stringify( lhLastResult ) } )
					.then( function ( res ) {
						sendBtn.disabled    = false;
						sendBtn.innerHTML   = '<span class="dashicons dashicons-cloud-upload"></span> Send to Webhook';
						showFeedback( feedback, res.data.message, res.success );
					} )
					.catch( function () {
						sendBtn.disabled    = false;
						sendBtn.innerHTML   = '<span class="dashicons dashicons-cloud-upload"></span> Send to Webhook';
						showFeedback( feedback, str.error, false );
					} );
			} );
		}
	}

	// ── Bulk Image Compression ────────────────────────────────────────────

	function humanBytes( bytes ) {
		bytes = bytes || 0;
		if ( bytes < 1024 ) return bytes + ' B';
		var units = [ 'KB', 'MB', 'GB' ];
		var i = -1;
		do { bytes /= 1024; i++; } while ( bytes >= 1024 && i < units.length - 1 );
		return bytes.toFixed( 1 ) + ' ' + units[ i ];
	}

	function escHtml( value ) {
		var div = document.createElement( 'div' );
		div.appendChild( document.createTextNode( String( value == null ? '' : value ) ) );
		return div.innerHTML;
	}

	function bindBulkCompress() {
		var startBtn   = document.getElementById( 'bcboost-compress-start' );
		var stopBtn    = document.getElementById( 'bcboost-compress-stop' );
		var wrap       = document.getElementById( 'bcboost-compress-progress-wrap' );
		var fill       = document.getElementById( 'bcboost-compress-progress-fill' );
		var label      = document.getElementById( 'bcboost-compress-progress-label' );
		var feedback   = document.getElementById( 'bcboost-compress-feedback' );
		var statsEl    = document.getElementById( 'bcboost-compress-stats' );
		var failuresEl = document.getElementById( 'bcboost-compress-failures' );

		if ( ! startBtn ) return;

		// When the button is disabled server-side, say WHY (no silent dead buttons).
		if ( ! cfg.webpSupported && cfg.webpSupportReason ) {
			startBtn.title = cfg.webpSupportReason;
		}

		var running = false;
		var retries = 0;
		var MAX_RETRIES = 3;

		function setProgress( p ) {
			if ( fill )  fill.style.width = ( p.percentage || 0 ) + '%';
			if ( label ) {
				var text = ( p.done || 0 ) + ' of ' + ( p.total || 0 ) +
					' images (' + ( p.percentage || 0 ) + '%) · ' + humanBytes( p.bytes_saved ) + ' saved';
				if ( p.failed ) {
					text += ' · ' + p.failed + ' failed';
				}
				label.textContent = text;
			}
			renderFailures( p );
		}

		// Collapsible "N images failed" list, updated live as batches report in.
		function renderFailures( p ) {
			if ( ! failuresEl ) return;
			var failed = p.failed || ( p.failures ? p.failures.length : 0 );
			if ( ! failed ) return;

			var wasOpen = false;
			var prev = failuresEl.querySelector( 'details' );
			if ( prev && prev.open ) wasOpen = true;

			var html = '<details' + ( wasOpen ? ' open' : '' ) + '>' +
				'<summary style="cursor:pointer;color:#f87171;">' +
				failed + ' ' + escHtml( str.imagesFailed || 'image(s) could not be converted — click for details' ) +
				'</summary><ul style="margin:8px 0 0 18px;font-size:12px;opacity:.85;">';
			( p.failures || [] ).forEach( function ( f ) {
				html += '<li>' + escHtml( f.file || '?' ) + ' — ' + escHtml( f.reason || 'unknown error' ) + '</li>';
			} );
			html += '</ul></details>';

			failuresEl.innerHTML = html;
			failuresEl.style.display = 'block';
		}

		function finish( p ) {
			running = false;
			startBtn.disabled = ! cfg.webpSupported;
			startBtn.style.display = '';
			if ( stopBtn ) stopBtn.style.display = 'none';
			if ( p ) {
				renderFailures( p );
				var summary = ( str.compressDone || 'Compression complete!' ) + ' ' +
					( p.generated || 0 ) + ' optimized copies created, ' + humanBytes( p.bytes_saved ) + ' saved.';
				if ( p.failed ) {
					summary += ' ' + p.failed + ' failed (see list below).';
				}
				showFeedback( feedback, summary, ! p.failed );
				if ( statsEl ) {
					statsEl.innerHTML = 'This run: <strong>' + ( p.generated || 0 ) +
						'</strong> optimized copies created, <strong>' + humanBytes( p.bytes_saved ) + '</strong> saved.';
				}
			}
		}

		function runBatch() {
			if ( ! running ) return;
			doAjax( 'BCBOOST_compress_batch' )
				.then( function ( res ) {
					retries = 0;
					if ( ! res.success ) {
						finish( null );
						showFeedback( feedback, ( res.data && res.data.message ) || str.error, false );
						return;
					}
					var p = res.data;
					setProgress( p );
					// Partial batches (server stopped early to dodge its time limit)
					// still report status "running" — just keep looping from the cursor.
					if ( p.status === 'running' ) {
						runBatch();
					} else {
						finish( p );
					}
				} )
				.catch( function () {
					// A transient hiccup (proxy timeout, dropped connection, one hard
					// server error) must not strand the whole run — retry with backoff.
					// The server's cursor + poison-pill guard make retries safe.
					if ( running && retries < MAX_RETRIES ) {
						retries++;
						showFeedback( feedback,
							( str.compressRetrying || 'Connection hiccup — retrying…' ) + ' (' + retries + '/' + MAX_RETRIES + ')',
							true );
						setTimeout( runBatch, 2000 * retries );
						return;
					}
					finish( null );
					showFeedback( feedback, str.error, false );
				} );
		}

		startBtn.addEventListener( 'click', function () {
			if ( running ) return;
			startBtn.disabled = true;
			startBtn.style.display = 'none';
			if ( wrap )    wrap.style.display = 'block';
			if ( stopBtn ) stopBtn.style.display = '';
			if ( failuresEl ) { failuresEl.style.display = 'none'; failuresEl.innerHTML = ''; }
			showFeedback( feedback, str.compressing || 'Compressing…', true );

			doAjax( 'BCBOOST_compress_start' )
				.then( function ( res ) {
					if ( ! res.success ) {
						finish( null );
						showFeedback( feedback, ( res.data && res.data.message ) || str.error, false );
						return;
					}
					running = true;
					retries = 0;
					setProgress( res.data );
					if ( res.data.total === 0 ) { finish( res.data ); return; }
					runBatch();
				} )
				.catch( function () { finish( null ); showFeedback( feedback, str.error, false ); } );
		} );

		if ( stopBtn ) {
			stopBtn.addEventListener( 'click', function () {
				running = false;
				doAjax( 'BCBOOST_compress_stop' ).then( function ( res ) {
					finish( res && res.success ? res.data : null );
				} ).catch( function () { finish( null ); } );
			} );
		}
	}

	// ── Ambient video backdrop (video-* themes) ──────────────────────────
	// Reduced-motion users get a paused first frame; hidden tabs pause the
	// loop (belt + braces over the browser's own throttling). Autoplay is
	// muted+playsinline so no browser blocks it.

	function initVideoBackdrop() {
		var vid = document.getElementById( 'bcboost-bg-video' );
		if ( ! vid ) return;

		if ( window.matchMedia && window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches ) {
			vid.autoplay = false;
			vid.addEventListener( 'loadeddata', function () { vid.pause(); }, { once: true } );
			vid.pause();
			return;
		}
		document.addEventListener( 'visibilitychange', function () {
			if ( document.hidden ) { vid.pause(); }
			else { vid.play().catch( function () {} ); }
		} );
	}

	// ── Appearance: live preview of accent colours + light/dark mode ─────
	// Updates the :root design tokens (every component re-tints instantly)
	// while the user picks. Persisting still happens through the normal
	// form save.

	function bindAppearance() {
		var form = document.getElementById( 'bcboost-appearance-form' );
		if ( ! form ) return;

		var root = document.documentElement;

		function shade( hex, amount ) {
			var m = /^#?([0-9a-f]{6})$/i.exec( hex );
			if ( ! m ) return hex;
			var n  = parseInt( m[ 1 ], 16 );
			var to = amount >= 0 ? 255 : 0;
			var f  = Math.abs( amount );
			var r  = Math.round( ( ( n >> 16 ) & 255 ) + ( to - ( ( n >> 16 ) & 255 ) ) * f );
			var g  = Math.round( ( ( n >> 8 ) & 255 ) + ( to - ( ( n >> 8 ) & 255 ) ) * f );
			var b  = Math.round( ( n & 255 ) + ( to - ( n & 255 ) ) * f );
			return '#' + ( ( 1 << 24 ) + ( r << 16 ) + ( g << 8 ) + b ).toString( 16 ).slice( 1 );
		}
		function rgba( hex, a ) {
			var s = hexToRgbString( hex, null );
			return s ? 'rgba(' + s + ',' + a + ')' : hex;
		}
		function applyColors( c ) {
			var set = root.style.setProperty.bind( root.style );
			set( '--bcboost-green',          c.primary );
			set( '--bcboost-green-dark',     shade( c.primary, -0.18 ) );
			set( '--bcboost-green-dim',      shade( c.primary, -0.55 ) );
			set( '--bcboost-green-glow',     rgba( c.primary, 0.25 ) );
			set( '--bcboost-green-glow-lg',  rgba( c.primary, 0.5 ) );
			set( '--bcboost-glass-green',    rgba( c.primary, 0.08 ) );
			set( '--bcboost-border-green',   rgba( c.primary, 0.30 ) );
			set( '--bcboost-success-light',  rgba( c.primary, 0.10 ) );
			set( '--bcboost-purple',         c.secondary );
			set( '--bcboost-purple-dark',    shade( c.secondary, -0.15 ) );
			set( '--bcboost-purple-light',   shade( c.secondary, 0.20 ) );
			set( '--bcboost-glass-purple',   rgba( c.secondary, 0.08 ) );
			set( '--bcboost-border-purple',  rgba( c.secondary, 0.30 ) );
			set( '--bcboost-blue',           c.info );
			set( '--bcboost-blue-light',     shade( c.info, 0.20 ) );
			set( '--bcboost-glass-blue',     rgba( c.info, 0.08 ) );
			set( '--bcboost-border-blue',    rgba( c.info, 0.30 ) );
			set( '--bcboost-cyan',           c.highlight );
		}

		function currentColors() {
			var c = {};
			form.querySelectorAll( '.bcboost-color-swatch-input' ).forEach( function ( input ) {
				c[ input.dataset.slot ] = input.value;
			} );
			return c;
		}

		// Colour picker ↔ hex text field two-way sync + live apply.
		form.querySelectorAll( '.bcboost-color-swatch-input' ).forEach( function ( input ) {
			var hexField = form.querySelector( '[data-hex-for="' + input.id + '"]' );

			input.addEventListener( 'input', function () {
				if ( hexField ) hexField.value = input.value;
				applyColors( currentColors() );
			} );

			if ( hexField ) {
				hexField.addEventListener( 'input', function () {
					var v = hexField.value.trim();
					if ( v && v[ 0 ] !== '#' ) v = '#' + v;
					if ( /^#[0-9a-f]{6}$/i.test( v ) ) {
						input.value = v.toLowerCase();
						applyColors( currentColors() );
					}
				} );
			}
		} );

		// Theme radio cards: keep the active outline in sync before save, and
		// live-preview the light/dark mode (backdrops are dark-based, so any
		// non-"light" pick previews dark; the motion engine itself swaps on save).
		form.querySelectorAll( '.bcboost-theme-card input[type="radio"]' ).forEach( function ( radio ) {
			radio.addEventListener( 'change', function () {
				form.querySelectorAll( '.bcboost-theme-card' ).forEach( function ( card ) {
					card.classList.toggle( 'bcboost-theme-card--active', card.contains( radio ) && radio.checked );
				} );
				if ( radio.name === 'admin_theme' && radio.checked ) {
					var light = radio.value === 'light';
					document.body.classList.toggle( 'bcboost-mode-light', light );
					document.body.classList.toggle( 'bcboost-mode-dark', ! light );
				}
			} );
		} );

		// Video swatches: motion previews on hover/focus only (never all six
		// looping at once — and reduced-motion users get static first frames).
		var reduceMotion = window.matchMedia && window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches;
		form.querySelectorAll( '.bcboost-theme-swatch--video' ).forEach( function ( vid ) {
			if ( reduceMotion ) return;
			var card = vid.closest( '.bcboost-theme-card' );
			if ( ! card ) return;
			var play  = function () { vid.play().catch( function () {} ); };
			var pause = function () { vid.pause(); };
			card.addEventListener( 'mouseenter', play );
			card.addEventListener( 'mouseleave', pause );
			card.addEventListener( 'focusin', play );
			card.addEventListener( 'focusout', pause );
		} );

		// Reset to shipped defaults (previews immediately; save persists).
		var resetBtn = document.getElementById( 'bcboost-appearance-reset' );
		if ( resetBtn ) {
			resetBtn.addEventListener( 'click', function () {
				var d = cfg.colorDefaults || {};
				form.querySelectorAll( '.bcboost-color-swatch-input' ).forEach( function ( input ) {
					var v = d[ input.dataset.slot ];
					if ( ! v ) return;
					input.value = v;
					var hexField = form.querySelector( '[data-hex-for="' + input.id + '"]' );
					if ( hexField ) hexField.value = v;
				} );
				applyColors( currentColors() );
			} );
		}
	}

	// ── Active nav highlight ──────────────────────────────────────────────

	function highlightActiveNav() {
		const current = window.location.search;
		document.querySelectorAll( '.bcboost-nav-item' ).forEach( function ( link ) {
			const href = link.getAttribute( 'href' ) || '';
			const match = href.split( '?' )[ 1 ] || '';
			if ( match && current.includes( match.split( '&' )[ 0 ] ) ) {
				link.classList.add( 'bcboost-nav-item--active' );
			}
		} );
	}

	// ── Toggle animation on hover for feature items ───────────────────────

	function initFeatureItemHover() {
		document.querySelectorAll( '.bcboost-feature-item' ).forEach( function ( item ) {
			item.addEventListener( 'mouseenter', function () {
				item.style.transform = 'translateX(4px)';
			} );
			item.addEventListener( 'mouseleave', function () {
				item.style.transform = '';
			} );
		} );
	}

	// ── Init ─────────────────────────────────────────────────────────────

	document.addEventListener( 'DOMContentLoaded', function () {
		initPageTransitions();
		initParticles();     // "Classic" backdrop only — dark/light modes render no canvas.
		initVideoBackdrop(); // video-* themes: reduced-motion + hidden-tab handling.
		bindAppearance();
		initStatCounters();
		initCardStagger();
		animateProgressBar();
		initBoostButtonPulse();
		initFeatureItemHover();
		highlightActiveNav();
		bindClearCache( '#bcboost-clear-cache, #bcboost-clear-cache-main' );
		bindDbCleanup();
		bindPreloader();
		bindWebhook();
		bindLighthouseWebhook();
		bindLighthouse();
		bindBulkCompress();
	} );

} )();
