/**
 * Blaminhor Essentials — Cookie Banner runtime.
 *
 * Renders the cookie consent UI inside a Shadow DOM root, manages the
 * signed first-party cookie, swaps blocked text/plain scripts on consent,
 * pushes Google Consent Mode v2 signals, and exposes the public API on
 * `window.blaminhor.consent`.
 */
( function () {
	'use strict';

	var CFG       = window.blaminhorCookiesConfig || {};
	var CATEGORIES = CFG.categories || [ 'essential', 'preferences', 'statistics', 'marketing' ];
	var COOKIE     = CFG.cookieName || 'blaminhor_cookies_consent';
	var TTL_DAYS   = CFG.cookieTtlDays || 395;
	var REST_ROOT  = CFG.restRoot || '/wp-json/blaminhor-essentials/cookies/v1/';
	var REST_NONCE = CFG.restNonce || '';
	var PREVIEW    = !! CFG.previewMode;

	var state = {
		host:           null,
		shadow:         null,
		mode:           'banner', // 'banner' | 'preferences' | 'hidden'
		context:        null,     // /context response
		choices:        defaultChoices(),
		services:       {},
		focusReturn:    null,
		listeners:      [],
	};

	function defaultChoices () {
		var c = {};
		CATEGORIES.forEach( function ( k ) {
			c[ k ] = ( k === 'essential' ) ? 1 : 0;
		} );
		return c;
	}

	// ---------------------------------------------------------------
	// Public API
	// ---------------------------------------------------------------

	window.blaminhor               = window.blaminhor || {};
	window.blaminhor.consent       = {
		get: function ( category ) {
			if ( ! CATEGORIES.indexOf( category ) === -1 ) {
				return 'unset';
			}
			return state.choices[ category ] ? 'allow' : 'deny';
		},
		all: function () { return Object.assign( {}, state.choices ); },
		hasConsented: function () { return !! readCookie(); },
		set: function ( category, value ) {
			if ( CATEGORIES.indexOf( category ) === -1 ) {
				return;
			}
			state.choices[ category ] = ( value === 'allow' || value === 1 || value === true ) ? 1 : 0;
			persist( 'banner' );
		},
		revoke: function () {
			state.choices = defaultChoices();
			document.cookie = COOKIE + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
			openPreferences();
		},
		openPreferences: openPreferences,
		onChange: function ( fn ) {
			if ( typeof fn === 'function' ) {
				state.listeners.push( fn );
			}
		}
	};

	window.blaminhor.services      = {
		isAllowed: function ( handle ) {
			if ( state.services[ handle ] !== undefined ) {
				return !! state.services[ handle ];
			}
			// fallback : check the service category default.
			var svc = ( CFG.services || [] ).filter( function ( s ) { return s.handle === handle; } )[ 0 ];
			return svc ? !! state.choices[ svc.category ] : false;
		},
		purposeAllowed: function ( purposeId ) {
			// Coarse mapping: TCF purpose 1 ("Store/access info") => essential.
			// Anything beyond that we treat as marketing by default.
			return purposeId === 1 ? true : !! state.choices.marketing;
		}
	};

	// ---------------------------------------------------------------
	// Boot
	// ---------------------------------------------------------------

	if ( window.blaminhorConsent && window.blaminhorConsent.choices ) {
		// Server already determined the visitor has valid consent —
		// don't render any UI, just hydrate state and unlock scripts.
		state.choices  = window.blaminhorConsent.choices;
		state.services = window.blaminhorConsent.services || {};
		ready( function () {
			unlockScripts();
			pushConsentMode();
		} );
		return;
	}

	ready( bootBanner );

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

	function bootBanner () {
		state.host = document.getElementById( 'blaminhor-cookies-root' );
		if ( ! state.host ) {
			state.host = document.createElement( 'div' );
			state.host.id = 'blaminhor-cookies-root';
			document.body.appendChild( state.host );
		}

		state.shadow = state.host.attachShadow ? state.host.attachShadow( { mode: 'open' } ) : state.host;
		injectShadowStyles();
		injectPreviewStrip();

		fetchContext().then( applyContext ).catch( function () {
			// Fallback: assume strict EU opt-in.
			renderBanner();
			showRevokeIcon();
		} );
	}

	function fetchContext () {
		return fetch( REST_ROOT + 'context', {
			method:  'GET',
			headers: { 'X-WP-Nonce': REST_NONCE },
			credentials: 'same-origin'
		} ).then( function ( r ) { return r.json(); } );
	}

	function applyContext ( ctx ) {
		state.context = ctx;
		// Pre-check categories per the preset.
		var pre = ( ctx.presetConfig && ctx.presetConfig.prechecked ) || [ 'essential' ];
		CATEGORIES.forEach( function ( k ) {
			state.choices[ k ] = pre.indexOf( k ) !== -1 ? 1 : 0;
		} );

		// GPC respected by preset => auto-refuse and skip banner.
		if ( ctx.gpcActive && ctx.presetConfig && ctx.presetConfig.gpc_respect ) {
			state.choices = defaultChoices();
			persist( 'gpc' );
			return;
		}

		renderBanner();
		showRevokeIcon();
	}

	// ---------------------------------------------------------------
	// Rendering
	// ---------------------------------------------------------------

	function injectShadowStyles () {
		if ( ! CFG.colors ) {
			return;
		}
		var s = document.createElement( 'style' );
		s.textContent = buildCss();
		state.shadow.appendChild( s );
	}

	function injectPreviewStrip () {
		if ( ! PREVIEW || ! state.shadow ) {
			return;
		}
		// Idempotent: remove any existing strip before re-injecting.
		var existing = state.shadow.querySelectorAll( '.blaminhor-cookies-preview-strip' );
		Array.prototype.forEach.call( existing, function ( n ) {
			if ( n.parentNode ) {
				n.parentNode.removeChild( n );
			}
		} );
		var t    = ( CFG.texts || {} );
		var wrap = document.createElement( 'div' );
		wrap.className = 'blaminhor-cookies-root';
		wrap.innerHTML =
			'<div class="blaminhor-cookies-preview-strip" role="status" aria-live="polite">' +
				'<span>' + esc( t.preview_banner || 'Preview mode — your choices are not saved' ) + '</span>' +
				'<button type="button" data-act="preview-exit">' + esc( t.preview_exit || 'Exit preview' ) + '</button>' +
			'</div>';
		state.shadow.appendChild( wrap );
		var btn = wrap.querySelector( '[data-act="preview-exit"]' );
		if ( btn ) {
			btn.addEventListener( 'click', function () {
				// Drop the preview query params and reload clean.
				window.location.href = window.location.pathname + window.location.hash;
			} );
		}
	}

	function buildCss () {
		var c        = CFG.colors;
		var darkMode = CFG.darkMode || 'auto';

		// Dark-mode overrides applied (a) always under `.dark-forced` when
		// `darkMode === 'dark'`, and (b) inside `@media (prefers-color-scheme: dark)`
		// when `darkMode === 'auto'`. Critically these include the secondary
		// button text color and category separators, which would otherwise
		// blend into the dark background.
		var darkRules = [
			'.blaminhor-cookies-banner,.blaminhor-cookies-banner.layout-popup .blaminhor-cookies-banner-inner,.blaminhor-cookies-prefs-inner{background:' + c.darkBackground + ';color:' + c.darkText + ';}',
			'.blaminhor-cookies-actions button.secondary{color:' + c.darkText + ';border-color:' + c.darkText + ';}',
			'.blaminhor-cookies-cat{border-top-color:rgba(255,255,255,.12);}',
			'.blaminhor-cookies-toggle .slider{background:#3a4048;}'
		].join( '' );

		var darkBlock = darkMode === 'dark'
			? darkRules
			: ( darkMode === 'auto'
				? '@media (prefers-color-scheme: dark){' + darkRules + '}'
				: '' );

		// We build a flat string instead of embedding a real <style> file
		// to keep the runtime self-contained inside the Shadow DOM.
		return [
			':host{all:initial;}',
			'.blaminhor-cookies-root{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;color:' + c.text + ';line-height:1.5;}',
			'.blaminhor-cookies-banner{position:fixed;left:0;right:0;bottom:0;z-index:2147483600;background:' + c.background + ';color:' + c.text + ';box-shadow:0 -10px 30px rgba(0,0,0,.15);padding:18px 24px;display:flex;flex-wrap:wrap;align-items:center;gap:16px;}',
			'.blaminhor-cookies-banner.layout-top{top:0;bottom:auto;box-shadow:0 10px 30px rgba(0,0,0,.15);}',
			'.blaminhor-cookies-banner.layout-box{left:auto;right:24px;bottom:24px;max-width:420px;border-radius:12px;}',
			'.blaminhor-cookies-banner.layout-popup{top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.55);align-items:center;justify-content:center;padding:24px;}',
			'.blaminhor-cookies-banner.layout-popup .blaminhor-cookies-banner-inner{max-width:560px;background:' + c.background + ';padding:24px;border-radius:14px;}',
			'.blaminhor-cookies-banner-inner{flex:1;min-width:240px;}',
			'.blaminhor-cookies-banner h2{margin:0 0 6px;font-size:18px;font-weight:600;}',
			'.blaminhor-cookies-banner p{margin:0 0 12px;font-size:14px;}',
			'.blaminhor-cookies-actions{display:flex;flex-wrap:wrap;gap:8px;}',
			'.blaminhor-cookies-actions button{cursor:pointer;font:inherit;font-weight:500;padding:10px 16px;border-radius:8px;border:1px solid ' + c.buttonBorder + ';background:' + c.buttonBg + ';color:' + c.buttonText + ';min-width:120px;}',
			'.blaminhor-cookies-actions button.secondary{background:transparent;color:' + c.text + ';border-color:' + c.text + ';}',
			'.blaminhor-cookies-actions button:hover{filter:brightness(1.08);}',
			'.blaminhor-cookies-actions button:focus-visible{outline:2px solid ' + c.buttonBorder + ';outline-offset:2px;}',
			'.blaminhor-cookies-prefs{position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:2147483601;display:flex;align-items:center;justify-content:center;padding:24px;}',
			'.blaminhor-cookies-prefs-inner{background:' + c.background + ';color:' + c.text + ';max-width:560px;width:100%;max-height:90vh;overflow:auto;border-radius:14px;padding:24px;}',
			'.blaminhor-cookies-prefs h2{margin:0 0 12px;font-size:20px;}',
			'.blaminhor-cookies-cat{padding:12px 0;border-top:1px solid rgba(0,0,0,.08);}',
			'.blaminhor-cookies-cat:first-of-type{border-top:0;}',
			'.blaminhor-cookies-cat-head{display:flex;align-items:center;justify-content:space-between;gap:12px;}',
			'.blaminhor-cookies-cat-head strong{font-size:15px;}',
			'.blaminhor-cookies-cat-desc{font-size:13px;opacity:.8;margin:4px 0 0;}',
			'.blaminhor-cookies-toggle{position:relative;width:42px;height:24px;flex:0 0 auto;}',
			'.blaminhor-cookies-toggle input{opacity:0;width:0;height:0;}',
			'.blaminhor-cookies-toggle .slider{position:absolute;cursor:pointer;inset:0;background:#ccc;border-radius:24px;transition:.2s;}',
			'.blaminhor-cookies-toggle .slider::before{content:"";position:absolute;height:18px;width:18px;left:3px;top:3px;background:white;border-radius:50%;transition:.2s;}',
			'.blaminhor-cookies-toggle input:checked+.slider{background:' + c.buttonBg + ';}',
			'.blaminhor-cookies-toggle input:checked+.slider::before{transform:translateX(18px);}',
			'.blaminhor-cookies-toggle input:disabled+.slider{opacity:.6;cursor:not-allowed;}',
			'.blaminhor-cookies-revoke{position:fixed;left:16px;bottom:16px;width:44px;height:44px;border-radius:50%;background:' + c.buttonBg + ';color:' + c.buttonText + ';display:flex;align-items:center;justify-content:center;cursor:pointer;border:none;z-index:2147483599;font-size:22px;box-shadow:0 4px 12px rgba(0,0,0,.2);}',
			'.blaminhor-cookies-iframe-placeholder{display:flex;align-items:center;justify-content:center;padding:24px;background:#f1f3f5;color:#1f2329;text-align:center;border:1px dashed #adb5bd;border-radius:8px;min-height:160px;}',
			'.blaminhor-cookies-preview-strip{position:fixed;top:0;left:0;right:0;z-index:2147483602;background:#fef3c7;color:#78350f;padding:8px 16px;font:500 13px/1.4 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;display:flex;align-items:center;justify-content:center;gap:12px;box-shadow:0 2px 8px rgba(0,0,0,.15);}',
			'.blaminhor-cookies-preview-strip button{background:transparent;border:1px solid #78350f;color:#78350f;padding:4px 10px;border-radius:6px;font:inherit;cursor:pointer;}',
			'.blaminhor-cookies-preview-strip button:hover{background:#78350f;color:#fef3c7;}',
			darkBlock,
			'@media (max-width:640px){.blaminhor-cookies-banner{flex-direction:column;align-items:stretch;}.blaminhor-cookies-actions{justify-content:stretch;}.blaminhor-cookies-actions button{flex:1;min-width:0;}}'
		].join( '' );
	}

	function renderBanner () {
		clearShadow();
		state.mode = 'banner';

		var layout = ( CFG.layout || 'banner-bottom' );
		var layoutClass = layout === 'banner-top' ? 'layout-top'
		                : layout === 'box-bottom-right' ? 'layout-box'
		                : layout === 'popup-center' ? 'layout-popup' : '';

		var t = ( CFG.texts || {} );
		var wrap = document.createElement( 'div' );
		wrap.className = 'blaminhor-cookies-root';

		var inner = '<div class="blaminhor-cookies-banner ' + layoutClass + '" role="dialog" aria-labelledby="blaminhor-cookies-title" aria-describedby="blaminhor-cookies-desc">' +
			'<div class="blaminhor-cookies-banner-inner">' +
				'<h2 id="blaminhor-cookies-title">' + esc( t.banner_title ) + '</h2>' +
				'<p id="blaminhor-cookies-desc">' + esc( t.banner_body ) + '</p>' +
				( state.context && state.context.gpcActive ?
					'<p style="font-size:13px;opacity:.8;">' + esc( t.gpc_acknowledged ) + '</p>' : '' ) +
			'</div>' +
			'<div class="blaminhor-cookies-actions">' +
				'<button type="button" data-act="reject" class="secondary">' + esc( t.reject_all ) + '</button>' +
				'<button type="button" data-act="customize" class="secondary">' + esc( t.customize ) + '</button>' +
				'<button type="button" data-act="accept">' + esc( t.accept_all ) + '</button>' +
			'</div>' +
		'</div>';

		wrap.innerHTML = inner;
		state.shadow.appendChild( wrap );

		wrap.querySelector( '[data-act="accept"]' ).addEventListener( 'click', acceptAll );
		wrap.querySelector( '[data-act="reject"]' ).addEventListener( 'click', rejectAll );
		wrap.querySelector( '[data-act="customize"]' ).addEventListener( 'click', openPreferences );

		// Focus the dialog for accessibility (WCAG 2.4.3).
		setTimeout( function () {
			var dlg = wrap.querySelector( '.blaminhor-cookies-banner' );
			if ( dlg ) {
				dlg.setAttribute( 'tabindex', '-1' );
				dlg.focus();
			}
		}, 50 );
	}

	function openPreferences () {
		state.focusReturn = document.activeElement;
		state.mode = 'preferences';
		clearShadow();
		injectShadowStyles();

		var t = ( CFG.texts || {} );
		var wrap = document.createElement( 'div' );
		wrap.className = 'blaminhor-cookies-root';

		var rows = CATEGORIES.map( function ( cat ) {
			var disabled = ( cat === 'essential' ) ? 'disabled' : '';
			var checked  = state.choices[ cat ] ? 'checked' : '';
			var label    = t[ 'category_' + cat ] || cat;
			var desc     = t[ 'category_' + cat + '_desc' ] || '';
			return '<div class="blaminhor-cookies-cat">' +
				'<div class="blaminhor-cookies-cat-head">' +
					'<strong>' + esc( label ) + '</strong>' +
					'<label class="blaminhor-cookies-toggle">' +
						'<input type="checkbox" data-cat="' + esc( cat ) + '" ' + checked + ' ' + disabled + ' aria-label="' + esc( label ) + '">' +
						'<span class="slider"></span>' +
					'</label>' +
				'</div>' +
				'<p class="blaminhor-cookies-cat-desc">' + esc( desc ) + ( cat === 'essential' ? ' — <em>' + esc( t.always_active ) + '</em>' : '' ) + '</p>' +
			'</div>';
		} ).join( '' );

		wrap.innerHTML =
			'<div class="blaminhor-cookies-prefs" role="dialog" aria-modal="true" aria-labelledby="blaminhor-cookies-prefs-title">' +
				'<div class="blaminhor-cookies-prefs-inner">' +
					'<h2 id="blaminhor-cookies-prefs-title">' + esc( t.preferences_title ) + '</h2>' +
					rows +
					'<div class="blaminhor-cookies-actions" style="margin-top:18px;justify-content:flex-end;">' +
						'<button type="button" data-act="reject" class="secondary">' + esc( t.reject_all ) + '</button>' +
						'<button type="button" data-act="save">' + esc( t.save_preferences ) + '</button>' +
						'<button type="button" data-act="accept">' + esc( t.accept_all ) + '</button>' +
					'</div>' +
				'</div>' +
			'</div>';
		state.shadow.appendChild( wrap );

		wrap.querySelectorAll( '[data-cat]' ).forEach( function ( cb ) {
			cb.addEventListener( 'change', function ( e ) {
				state.choices[ e.target.dataset.cat ] = e.target.checked ? 1 : 0;
			} );
		} );
		wrap.querySelector( '[data-act="save"]' ).addEventListener( 'click', function () { persist( 'banner' ); } );
		wrap.querySelector( '[data-act="accept"]' ).addEventListener( 'click', acceptAll );
		wrap.querySelector( '[data-act="reject"]' ).addEventListener( 'click', rejectAll );

		// Focus trap for the modal.
		setTimeout( function () {
			var first = wrap.querySelector( 'input,button' );
			if ( first ) {
				first.focus();
			}
		}, 50 );
		wrap.addEventListener( 'keydown', function ( e ) {
			if ( e.key !== 'Tab' ) {
				return;
			}
			var focusable = wrap.querySelectorAll( 'input,button' );
			if ( ! focusable.length ) {
				return;
			}
			var first = focusable[ 0 ];
			var last  = focusable[ focusable.length - 1 ];
			if ( e.shiftKey && document.activeElement === first ) {
				e.preventDefault(); last.focus();
			} else if ( ! e.shiftKey && document.activeElement === last ) {
				e.preventDefault(); first.focus();
			}
		} );
	}

	function showRevokeIcon () {
		if ( ! CFG.showRevokeIcon ) {
			return;
		}
		var btn = document.createElement( 'button' );
		btn.type = 'button';
		btn.className = 'blaminhor-cookies-revoke';
		btn.setAttribute( 'aria-label', ( CFG.texts && CFG.texts.revoke_button ) || 'Cookie settings' );
		btn.textContent = '🍪';
		btn.addEventListener( 'click', openPreferences );

		var host = document.createElement( 'div' );
		host.className = 'blaminhor-cookies-root';
		host.appendChild( btn );
		state.shadow.appendChild( host );
	}

	function clearShadow () {
		while ( state.shadow.firstChild ) {
			state.shadow.removeChild( state.shadow.firstChild );
		}
		injectShadowStyles();
		injectPreviewStrip();
	}

	// ---------------------------------------------------------------
	// Actions
	// ---------------------------------------------------------------

	function acceptAll () {
		CATEGORIES.forEach( function ( k ) { state.choices[ k ] = 1; } );
		persist( 'banner' );
	}

	function rejectAll () {
		CATEGORIES.forEach( function ( k ) { state.choices[ k ] = ( k === 'essential' ) ? 1 : 0; } );
		persist( 'banner' );
	}

	function persist ( source ) {
		if ( PREVIEW ) {
			// Admin preview: simulate the close-the-banner effect without
			// writing the cookie, hitting the REST log, unlocking scripts
			// or firing GCM. The admin's real consent state stays intact.
			if ( window.console && console.info ) {
				console.info( '[Blaminhor] Preview mode — choice not persisted (' + source + ')' );
			}
			teardownUi();
			return;
		}
		writeCookie();
		notifyListeners( source );
		unlockScripts();
		upgradeIframes();
		pushConsentMode();
		bridgeWpConsentApi();
		sendLog( source );
		teardownUi();
	}

	// ---------------------------------------------------------------
	// Cookie persistence (client side; signature is added server-side
	// via the REST log endpoint which returns the canonical signed
	// payload — but for the no-reload path we also write a local cookie
	// without HMAC so subsequent page loads see the consent).
	// ---------------------------------------------------------------

	function readCookie () {
		var match = document.cookie.match( new RegExp( '(?:^|; )' + COOKIE + '=([^;]*)' ) );
		return match ? decodeURIComponent( match[ 1 ] ) : null;
	}

	function writeCookie () {
		// We do NOT HMAC client-side (no shared secret in JS). The signed
		// canonical cookie is written by the server through Set-Cookie on
		// the next request. Until then, this local cookie carries the
		// state for our own JS (the server validation will simply reject
		// it as unsigned and treat it as expired — fine because by then
		// the visitor's choices have already been applied client-side).
		var payload = JSON.stringify( {
			v: 1, t: Math.floor( Date.now() / 1000 ),
			c: state.choices, s: state.services,
			p: CFG.policyVersion, l: ( CFG.lang || '' ), src: 'banner'
		} );
		var b64     = btoa( unescape( encodeURIComponent( payload ) ) )
		              .replace( /\+/g, '-' ).replace( /\//g, '_' ).replace( /=+$/, '' );
		var maxAge  = TTL_DAYS * 86400;
		document.cookie = COOKIE + '=' + b64 + '.unsigned; max-age=' + maxAge +
			'; path=/; SameSite=Lax' + ( location.protocol === 'https:' ? '; Secure' : '' );
	}

	function unlockScripts () {
		var scripts = document.querySelectorAll( 'script[type="text/plain"][data-cookieconsent]' );
		scripts.forEach( function ( s ) {
			var cat = s.getAttribute( 'data-cookieconsent' );
			if ( ! state.choices[ cat ] ) {
				return;
			}
			var fresh = document.createElement( 'script' );
			Array.from( s.attributes ).forEach( function ( a ) {
				if ( a.name === 'type' || a.name === 'data-cookieconsent' ) {
					return;
				}
				fresh.setAttribute( a.name, a.value );
			} );
			fresh.text = s.textContent || '';
			s.parentNode.replaceChild( fresh, s );
		} );
	}

	function upgradeIframes () {
		var placeholders = document.querySelectorAll( '.blaminhor-cookies-iframe-placeholder' );
		placeholders.forEach( function ( ph ) {
			var cat = ph.dataset.category;
			if ( ! state.choices[ cat ] ) {
				return;
			}
			var ifr = document.createElement( 'iframe' );
			ifr.src    = ph.dataset.src;
			ifr.width  = ph.dataset.width || '';
			ifr.height = ph.dataset.height || '';
			ifr.setAttribute( 'loading', 'lazy' );
			ifr.setAttribute( 'referrerpolicy', 'no-referrer-when-downgrade' );
			ph.parentNode.replaceChild( ifr, ph );
		} );
	}

	function pushConsentMode () {
		if ( ! CFG.gcmEnabled ) {
			return;
		}
		window.dataLayer = window.dataLayer || [];
		function gtag () { window.dataLayer.push( arguments ); }
		gtag( 'consent', 'update', {
			ad_storage:           state.choices.marketing  ? 'granted' : 'denied',
			ad_user_data:         state.choices.marketing  ? 'granted' : 'denied',
			ad_personalization:   state.choices.marketing  ? 'granted' : 'denied',
			analytics_storage:    state.choices.statistics ? 'granted' : 'denied',
			functionality_storage: state.choices.preferences ? 'granted' : 'denied',
			personalization_storage: state.choices.preferences ? 'granted' : 'denied',
			security_storage:     'granted'
		} );
	}

	function bridgeWpConsentApi () {
		// Mirror our categories into the WP Consent API spec when available.
		if ( typeof window.wp_set_consent === 'function' ) {
			window.wp_set_consent( 'functional',          'allow' );
			window.wp_set_consent( 'preferences',         state.choices.preferences ? 'allow' : 'deny' );
			window.wp_set_consent( 'statistics',          state.choices.statistics  ? 'allow' : 'deny' );
			window.wp_set_consent( 'statistics-anonymous', 'allow' );
			window.wp_set_consent( 'marketing',           state.choices.marketing   ? 'allow' : 'deny' );
		}
	}

	function notifyListeners ( source ) {
		state.listeners.forEach( function ( fn ) {
			try { fn( state.choices, null, source ); } catch ( e ) { /* swallow */ }
		} );
	}

	function sendLog ( source ) {
		fetch( REST_ROOT + 'log', {
			method:  'POST',
			headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': REST_NONCE },
			credentials: 'same-origin',
			body: JSON.stringify( {
				choices:  state.choices,
				services: state.services,
				source:   source || 'banner',
				lang:     CFG.lang || '',
			} )
		} ).catch( function () { /* best-effort */ } );
	}

	function teardownUi () {
		clearShadow();
		state.mode = 'hidden';
		if ( state.focusReturn && state.focusReturn.focus ) {
			state.focusReturn.focus();
		}
		showRevokeIcon();
	}

	// ---------------------------------------------------------------
	// Utility
	// ---------------------------------------------------------------

	function esc ( s ) {
		if ( s === undefined || s === null ) {
			return '';
		}
		return String( s )
			.replace( /&/g, '&amp;' )
			.replace( /</g, '&lt;' )
			.replace( />/g, '&gt;' )
			.replace( /"/g, '&quot;' )
			.replace( /'/g, '&#039;' );
	}
} )();
