'use strict';
/*
 * Opt in (e.g. EU):
 * default all scripts disabled.
 * cookie banner
 *
 * Opt out (e.g. US):
 * default all scripts enabled
 * opt out cookie banner
 *
 * Other regions:
 * default all scripts enabled
 * no banner
 *
 *
 * For examples to edit the behaviour of the banner, please see https://github.com/really-Simple-Plugins/complianz-integrations
 * */

// Cookie cleanup data - declared at top level to avoid TDZ issues
let cmplz_cookie_data = [];
let cmplzCleanCookieInterval;

/*
 * Debug logger. Only emits when SCRIPT_DEBUG is enabled (complianz.debug),
 * so diagnostics stay available for developers without spamming visitors.
 * @param args
 */
function cmplz_log( ...args ) {
	if ( typeof complianz === 'undefined' || ! complianz.debug ) {
		return;
	}
	// eslint-disable-next-line no-console
	console.warn( ...args );
}

/*
 * Create an element
 * @param el
 * @param content
 * @returns {*}
 */
function cmplz_create_element( el, content ) {
	const obj = document.createElement( el );
	obj.innerHtml = content;
	return obj;
}

/*
 * Add an event
 * @param event
 * @param selector
 * @param callback
 * @param context
 */
function cmplz_add_event( event, selector, callback ) {
	document.addEventListener( event, ( e ) => {
		if ( e.target.closest( selector ) ) {
			callback( e );
		}
	} );
}

/*
 * Check if the element is hidden
 * @param el
 * @returns {boolean}
 */
function cmplz_is_hidden( el ) {
	return ( el.offsetParent === null );
}

function cmplz_html_decode( input ) {
	const doc = new DOMParser().parseFromString( input, 'text/html' );
	return doc.documentElement.textContent;
}

/*
 * Sanitize placeholder HTML before injecting it into DOM.
 * Keeps expected markup but removes scripts and risky attributes.
 */
function cmplz_sanitize_placeholder_html( input ) {
	const template = document.createElement( 'template' );
	template.innerHTML = String( input || '' );

	// Remove active content elements entirely.
	template.content.querySelectorAll( 'script, iframe, object, embed, link, style, base, meta' ).forEach( ( el ) => {
		el.remove();
	} );

	template.content.querySelectorAll( '*' ).forEach( ( el ) => {
		Array.from( el.attributes ).forEach( ( attr ) => {
			const attrName = attr.name.toLowerCase();

			// Remove inline event handlers like onclick.
			if ( attrName.indexOf( 'on' ) === 0 ) {
				el.removeAttribute( attr.name );
				return;
			}

			// Block javascript: and data: URLs on URL-based attributes.
			const attrValue = attr.value.trim();
			if ( ( attrName === 'href' || attrName === 'src' || attrName === 'xlink:href' ) &&
				/^\s*(javascript:|data:)/i.test( attrValue ) ) {
				el.removeAttribute( attr.name );
			}
		} );
	} );

	return template.innerHTML;
}

/*
 * Validate dynamic script URLs before assigning to script.src.
 * Only allow http(s) URLs (including relative URLs resolved by URL()).
 */
function cmplz_is_safe_script_src( scriptUrl ) {
	if ( typeof scriptUrl !== 'string' ) {
		return false;
	}

	const trimmed = scriptUrl.trim();
	if ( ! trimmed ) {
		return false;
	}

	try {
		const parsedUrl = new URL( trimmed, window.location.href );
		return parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:';
	} catch ( e ) {
		return false;
	}
}

/*
 * Focus trap functions for cookie banner modal
 */

function cmplz_trap_focus() {
	if ( ! cmplz_banner ) {
		return;
	}

	// Store current active element
	cmplz_original_active_element = cmplz_banner.ownerDocument.activeElement;

	// Add class to body for CSS targeting
	if ( complianz.soft_cookiewall ) {
		document.body.classList.add( 'cmplz-banner-active' );
	}

	// Add keydown listener at document level so it fires regardless of propagation
	document.addEventListener( 'keydown', cmplz_handle_tab_navigation );

	// Focus first focusable element in banner
	const focusableElements = cmplz_banner.querySelectorAll(
		'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
	);
	if ( focusableElements.length > 0 ) {
		focusableElements[ 0 ].focus();
	}
}

function cmplz_handle_tab_navigation( e ) {
	if ( e.key !== 'Tab' || ! cmplz_banner ) {
		return;
	}

	const focusableElements = Array.from( cmplz_banner.querySelectorAll(
		'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
	) ).filter( function( el ) {
		return el.offsetParent !== null;
	} );

	if ( focusableElements.length === 0 ) {
		e.preventDefault();
		return;
	}

	const firstElement = focusableElements[ 0 ];
	const lastElement = focusableElements[ focusableElements.length - 1 ];
	const activeElement = cmplz_banner.ownerDocument.activeElement;
	const focusIsInBanner = cmplz_banner.contains( activeElement );

	if ( e.shiftKey ) {
		if ( ! focusIsInBanner || activeElement === firstElement ) {
			e.preventDefault();
			lastElement.focus();
		}
	} else if ( ! focusIsInBanner || activeElement === lastElement ) {
		e.preventDefault();
		firstElement.focus();
	}
}

function cmplz_release_focus_trap() {
	document.removeEventListener( 'keydown', cmplz_handle_tab_navigation );

	// Remove class from body
	document.body.classList.remove( 'cmplz-banner-active' );

	const triggerSource = cmplz_banner_trigger_source;
	cmplz_banner_trigger_source = 'page-load';

	if ( triggerSource === 'manage-consent' ) {
		// Return focus to the button that opened the dialog
		if ( cmplz_original_active_element && cmplz_original_active_element.focus ) {
			try {
				cmplz_original_active_element.focus();
			} catch ( e ) {
				document.body.focus();
			}
		}
	} else if ( cmplz_last_input_was_keyboard ) {
		// Page-load + keyboard: move focus to the skip-navigation link so keyboard
		// users can proceed. Skipped for mouse/touch to avoid a visible skip-link flash.
		const skipLink = document.querySelector(
			'a[href="#content"], a[href="#main"], a[href="#main-content"], .skip-link'
		);
		if ( skipLink ) {
			skipLink.focus();
		} else {
			document.body.focus();
		}
	}
}
/**
 * Consent Area management for shortcode and block editor
 */
document.querySelectorAll( '.cmplz-consent-area.cmplz-placeholder' ).forEach( ( cmplzConsentArea ) => {
	cmplzConsentArea.addEventListener( 'click', ( e ) => {
		let container = e.target;
		if ( ! container.classList.contains( 'cmplz-consent-area' ) ) {
			container = e.target.closest( '.cmplz-consent-area.cmplz-placeholder' );
		}
		if ( container ) {
			const consentedService = container.getAttribute( 'data-service' );
			cmplz_set_service_consent( consentedService, true );
			cmplzLoadConsentAreaContent( false, consentedService );
			cmplz_enable_category( null, consentedService );
			cmplz_set_banner_status( 'dismissed' );
		}
	} );

	document.addEventListener( 'cmplz_enable_category', function( consentData ) {
		const consentedCategory = consentData.detail.category;
		const consentedService = consentData.detail.service;
		cmplzLoadConsentAreaContent( consentedCategory, consentedService );
	} );
} );

function cmplzLoadConsentAreaContent( consentedCategory, consentedService ) {
	document.querySelectorAll( '.cmplz-consent-area.cmplz-placeholder' ).forEach( ( obj ) => {
		const category = obj.getAttribute( 'data-category' );
		const service = obj.getAttribute( 'data-service' );
		const postId = obj.getAttribute( 'data-post_id' );
		const blockId = obj.getAttribute( 'data-block_id' );
		if ( consentedCategory === category || consentedService === service ) {
			//if not stored yet, load. As features in the user object can be changed on updates, we also check for the version
			const request = new XMLHttpRequest();
			request.open( 'GET', complianz.url + 'consent-area/' + postId + '/' + blockId, true );
			request.setRequestHeader( 'Content-type', 'application/json' );
			request.send();
			obj.classList.remove( 'cmplz-placeholder' );
			request.onload = function() {
				obj.innerHTML = JSON.parse( request.response );
				//search for script elements and execute them
				obj.querySelectorAll( 'script' ).forEach( ( scriptEl ) => {
					cmplz_run_script( scriptEl.innerHTML, category, service, 'inline', scriptEl );
				} );
			};
		}
	} );
}

/*
 * If an anchor is passed for an element which may load only after an ajax call, make sure it will scroll into view.
 */
document.addEventListener( 'cmplz_manage_consent_container_loaded', function() {
	const url = window.location.href;
	if ( url.indexOf( '#' ) !== -1 ) {
		const end_pos = url.lastIndexOf( '?' ) !== -1 ? url.lastIndexOf( '?' ) : undefined;
		const anchor = url.substring( url.indexOf( '#' ) + 1, end_pos );
		const element = document.getElementById( anchor );
		if ( element ) {
			const y = element.getBoundingClientRect().top + window.pageYOffset - 200;
			window.scrollTo( { top: y, behavior: 'smooth' } );
		}
	}
} );

/*
 * prevent caching of the WP Rest API by varnish or other caching tools
 */
complianz.locale = complianz.locale + '&token=' + Math.random().toString( 36 ).replace( /[^a-z]+/g, '' ).substr( 0, 5 );

/*
 * CustomEvent() polyfill
 * https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
 */
( function() {
	if ( typeof window.CustomEvent === 'function' ) {
		return false;
	}
	function CustomEvent( event, params ) {
		params = params || { bubbles: false, cancelable: false, detail: undefined };
		const evt = document.createEvent( 'CustomEvent' );
		evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
		return evt;
	}
	CustomEvent.prototype = window.Event.prototype;
	window.CustomEvent = CustomEvent;
}() );

let cmplz_banner;//look this one up when the cookiebanner loads.
const cmplz_banner_container = document.getElementById( 'cmplz-cookiebanner-container' );
let cmplz_manage_consent_button;
const cmplz_waiting_inline_scripts = [];
const cmplz_waiting_scripts = [];
const cmplz_fired_scripts = [];
let cmplz_placeholder_class_index = 0;
let cmplz_all_scripts_hook_fired = false;
let cmplz_consent_stored_once = false;
const cmplz_fired_category_events = [ 'functional' ];
const cmplz_fired_service_events = [];
const cmplz_categories = [
	'functional',
	'preferences',
	'statistics',
	'marketing',
];

// Focus trap variables
let cmplz_original_active_element = null;
let cmplz_banner_trigger_source = 'page-load'; // 'page-load' | 'manage-consent'

// Track the last input modality so we only move focus to the skip link when
// the banner was dismissed via keyboard. Mouse/touch users keep their focus
// where the click left it, avoiding a visible skip-link flash.
let cmplz_last_input_was_keyboard = false;
document.addEventListener( 'keydown', function() {
	cmplz_last_input_was_keyboard = true;
}, true );
document.addEventListener( 'pointerdown', function() {
	cmplz_last_input_was_keyboard = false;
}, true );

/*
 * Get a cookie by name
 * @param name
 * @returns {string}
 */

window.cmplz_get_cookie = function( name ) {
	if ( typeof document === 'undefined' ) {
		return '';
	}
	name = complianz.prefix + name;
	const cookies = document.cookie.split( ';' );
	for ( let i = 0; i < cookies.length; i++ ) {
		const cookie = cookies[ i ].trim();
		if ( cookie.startsWith( name + '=' ) ) {
			return cookie.substring( name.length + 1 );
		}
	}
	return '';
};

/*
 * set a cookie
 * @param name
 * @param value
 * @param use_prefix
 */

window.cmplz_set_cookie = function( name, value, use_prefix ) {
	if ( typeof document === 'undefined' ) {
		return;
	}

	use_prefix = typeof use_prefix !== 'undefined' ? use_prefix : true;
	const secure = window.location.protocol === 'https:' ? ';secure' : '';
	const date = new Date();
	date.setTime( date.getTime() + ( complianz.cookie_expiry * 24 * 60 * 60 * 1000 ) );
	const expires = ';expires=' + date.toGMTString();

	const domain = cmplz_get_cookie_domain();
	const domainString = domain.length > 0 ? `;domain=${ domain }` : '';

	const prefix = use_prefix ? complianz.prefix : '';
	const cookiePath = cmplz_get_cookie_path();

	document.cookie = `${ prefix }${ name }=${ value };SameSite=Lax${ secure }${ expires }${ domainString };path=${ cookiePath }`;
};

/*
 * Check if needle occurs in the haystack
 * @param needle
 * @param haystack
 * @returns {boolean}
 */
window.cmplz_in_array = function( needle, haystack ) {
	return haystack.includes( needle );
};

/*
 * Retrieve the highest level of consent that has been given
 *
 * */

window.cmplz_highest_accepted_category = function() {
	const consentedCategories = cmplz_accepted_categories();
	const priorityCategories = [ 'marketing', 'statistics', 'preferences' ];

	for ( let i = 0; i < priorityCategories.length; i++ ) {
		if ( cmplz_in_array( priorityCategories[ i ], consentedCategories ) ) {
			return priorityCategories[ i ];
		}
	}
	return 'functional';
};

/*
 * Sets all accepted categories as class in body
 */

const cmplz_set_category_as_body_class = () => {
	const classList = document.body.classList;
	for ( let i = classList.length - 1; i >= 0; i-- ) {
		if ( classList[ i ].startsWith( 'cmplz-' ) && classList[ i ] !== 'cmplz-document' ) {
			classList.remove( classList[ i ] );
		}
	}
	const cats = cmplz_accepted_categories();
	Object.values( cats ).forEach( ( category ) => {
		if ( typeof category === 'string' ) {
			classList.add( 'cmplz-' + category );
		}
	} );
	const services = cmplz_get_all_service_consents();
	Object.entries( services ).forEach( ( [ service, consent ] ) => {
		if ( consent ) {
			classList.add( 'cmplz-' + service );
		}
	} );

	classList.add( 'cmplz-' + complianz.region, 'cmplz-' + complianz.consenttype );
	const event = new CustomEvent( 'cmplz_set_category_as_bodyclass' );
	document.dispatchEvent( event );
};

const cmplz_append_css = ( css ) => {
	const head = document.head || document.getElementsByTagName( 'head' )[ 0 ];
	const style = document.createElement( 'style' );
	style.setAttribute( 'type', 'text/css' );
	style.appendChild( document.createTextNode( css ) );
	head.appendChild( style );
};

const cmplz_load_css = ( path ) => {
	const head = document.head || document.getElementsByTagName( 'head' )[ 0 ];
	const targetObj = document.createElement( 'link' );
	targetObj.rel = 'stylesheet';
	targetObj.type = 'text/css';
	targetObj.href = path;
	head.appendChild( targetObj );
};

/*
 * Run script, src or inline
 * @param script //src or inline script
 * @param category
 * @param type
 */

function cmplz_run_script( script, category, service, type, sourceObj ) {
	const targetObj = document.createElement( 'script' );
	if ( type !== 'inline' && cmplz_is_safe_script_src( script ) ) {
		targetObj.src = script;
	} else {
		if ( typeof script !== 'string' ) {
			script = script.innerHTML;
		}
		targetObj.textContent = [ script, 'cmplzScriptLoaded();' ].join( '\n' );
	}

	//check if already fired
	if ( cmplz_in_array( script, cmplz_fired_scripts ) ) {
		return;
	}

	cmplzCopyAttributes( sourceObj, targetObj );
	try {
		if ( type !== 'inline' ) {
			targetObj.onload = function() {
				cmplz_run_after_all_scripts( category, service );
				cmplz_maybe_run_waiting_scripts( script, category, service, sourceObj );
			};
		} else {
			window.cmplzScriptLoaded = function() {
				cmplz_run_after_all_scripts( category, service );
				cmplz_maybe_run_waiting_scripts( script, category, service, sourceObj );
			};
		}
		document.head.appendChild( targetObj );
	} catch ( exception ) {
		//only runs in case of error
		cmplz_run_after_all_scripts( category, service );
		throw 'Something went wrong ' + exception + ' while loading ' + script;
	}
}

/*
 * Check if there are waiting scripts, and if so, run them.
 * @param script //src or inline script
 * @param category
 */

function cmplz_maybe_run_waiting_scripts( script, category, service, sourceObj ) {
	const waitingScript = cmplz_get_waiting_script( cmplz_waiting_scripts, script );
	if ( waitingScript ) {
		cmplz_run_script( waitingScript, category, service, 'src', sourceObj );
	}

	const waiting_inline_script = cmplz_get_waiting_script( cmplz_waiting_inline_scripts, script );
	if ( waiting_inline_script ) {
		cmplz_run_script( waiting_inline_script, category, service, 'inline', sourceObj );
	}
}

const cmplzLazyLoader = () => {
	// Get all elements with the "lazy-load" class
	const cmplzLazyLoadElements = document.querySelectorAll( '.cmplz-blocked-content-container' );

	// Create an Intersection Observer instance
	const cmplzObserver = new IntersectionObserver( ( entries, observer ) => {
		entries.forEach( ( entry ) => {
			if ( entry.isIntersecting ) {
				// When the element is in view, load the background image
				const element = entry.target;
				const src = element.getAttribute( 'data-placeholder-image' );
				if ( src ) {
					const index = element.getAttribute( 'data-placeholder_class_index' );
					cmplz_append_css( '.cmplz-placeholder-' + index + ' {background-image: url(' + src + ') !important;}' );
					cmplz_set_blocked_content_container_aspect_ratio( element, src, index );
				}

				// Stop observing the element
				observer.unobserve( element );
			}
		} );
	} );

	// Start observing each lazy-load element
	cmplzLazyLoadElements.forEach( ( element ) => {
		cmplzObserver.observe( element );
	} );
};

/*
 * Set placeholder image as background on the parent div, set notice, and handle height.
 *
 * */

function cmplz_set_blocked_content_container() {
	//to prevent this function to twice run on an element, we add an attribute to each element that has been processed.
	//then skip elements with that element.
	document.querySelectorAll( '.cmplz-image' ).forEach( ( obj ) => {
		if ( obj.classList.contains( 'cmplz-processed' ) ) {
			return;
		}
		obj.classList.add( 'cmplz-processed' );
		const service = obj.getAttribute( 'data-service' );
		const category = obj.getAttribute( 'data-category' );
		const blocked_image_container = obj.parentElement;
		blocked_image_container.classList.add( 'cmplz-blocked-content-container' );
		const curIndex = blocked_image_container.getAttribute( 'data-placeholder_class_index' );
		//handle browser native lazy load feature
		if ( obj.getAttribute( 'loading' ) === 'lazy' ) {
			obj.removeAttribute( 'loading' );
			obj.setAttribute( 'data-deferlazy', 1 );
		}

		if ( curIndex === null ) {
			cmplz_placeholder_class_index++;
			blocked_image_container.classList.add( 'cmplz-placeholder-' + cmplz_placeholder_class_index, 'cmplz-blocked-content-container' );
			blocked_image_container.setAttribute( 'data-placeholder_class_index', cmplz_placeholder_class_index );
			cmplz_insert_placeholder_text( blocked_image_container, category, service );
		}
	} );

	document.querySelectorAll( '.cmplz-placeholder-element' ).forEach( ( obj ) => {
		if ( obj.classList.contains( 'cmplz-processed' ) ) {
			return;
		}
		obj.classList.add( 'cmplz-processed' );
		const service = obj.getAttribute( 'data-service' );
		const category = obj.getAttribute( 'data-category' );

		//we set this element as container with placeholder image
		let blocked_content_container;
		if ( obj.classList.contains( 'cmplz-iframe' ) ) {
			//handle browser native lazy load feature
			if ( obj.getAttribute( 'loading' ) === 'lazy' ) {
				obj.removeAttribute( 'loading' );
				obj.setAttribute( 'data-deferlazy', 1 );
			}
			blocked_content_container = obj.parentElement;
		} else {
			blocked_content_container = obj;
		}
		const curIndex = blocked_content_container.getAttribute( 'data-placeholder_class_index' );
		//if the blocked content container class is already added, don't add it again
		if ( curIndex === null ) {
			cmplz_placeholder_class_index++;
			blocked_content_container.classList.add( 'cmplz-placeholder-' + cmplz_placeholder_class_index, 'cmplz-blocked-content-container' );
			blocked_content_container.setAttribute( 'data-placeholder_class_index', cmplz_placeholder_class_index );
			cmplz_insert_placeholder_text( blocked_content_container, category, service );
			//handle image size for video
			const src = obj.getAttribute( 'data-placeholder-image' );
			if ( src && typeof src !== 'undefined' && src.length ) {
				//move src to parent, if needed.
				blocked_content_container.setAttribute( 'data-placeholder-image', src );
			}
		}
	} );

	cmplzLazyLoader();

	/*
	 * In some cases, like ajax loaded content, the placeholders are initialized again. In that case, the scripts may need to be fired again as well.
	 * In case of an opt-out region and Do Not Track, a consent check will return 'allow', because it doesn't take DNT into account
	 */

	if ( cmplz_has_consent( 'statistics' ) ) {
		cmplz_enable_category( 'statistics' );
	}

	if ( cmplz_has_consent( 'marketing' ) ) {
		cmplz_enable_category( 'marketing' );
	}
}

function cmplz_insert_placeholder_text( container, category, service ) {
	if ( ! container.querySelector( '.cmplz-blocked-content-notice' ) ) {
		let placeholder_text = complianz.placeholdertext;
		category = category || 'marketing';
		let body;
		if ( typeof placeholder_text !== 'undefined' ) {
			if ( Number( complianz.clean_cookies ) === 1 ) {
				//make service human readable
				let service_nicename = service ? service.replace( '-', ' ' ) : '';
				service_nicename = service_nicename.charAt( 0 ).toUpperCase() + service_nicename.slice( 1 );
				placeholder_text = placeholder_text.replace( '{service}', service_nicename );
				body = cmplz_create_element( 'div', placeholder_text );
				body.innerHTML = cmplz_sanitize_placeholder_html( placeholder_text );
				body.classList.add( 'cmplz-blocked-content-notice' );
				const btn = body.querySelector( 'button' );
				btn.setAttribute( 'data-service', service );
				btn.setAttribute( 'data-category', category );
				btn.setAttribute( 'aria-label', complianz.aria_label.replace( '{service}', service_nicename ) );
				const pageLinks = complianz.page_links[ complianz.region ];
				const link = body.querySelector( '.cmplz-links a' );
				if ( pageLinks && pageLinks.hasOwnProperty( 'cookie-statement' ) ) {
					link.setAttribute( 'href', pageLinks[ 'cookie-statement' ].url );
					if ( link.innerText === '{title}' ) {
						link.innerText = pageLinks[ 'cookie-statement' ].title;
					}
				} else if ( link ) {
					link.parentElement.style.display = 'none';
				}
			} else {
				const btn = cmplz_create_element( 'button', '' );
				const category_nicename = complianz.categories.hasOwnProperty( category ) ? complianz.categories[ category ] : 'marketing';
				btn.innerText = placeholder_text.replace( '{category}', category_nicename );
				btn.classList.add( 'cmplz-blocked-content-notice', 'cmplz-accept-category', 'cmplz-accept-' + category ); //'cmplz-accept-'+category is deprecated

				btn.setAttribute( 'data-service', service );
				btn.setAttribute( 'data-category', category );
				btn.setAttribute( 'aria-label', complianz.aria_label.replace( '{category}', category ) );
				body = btn;
			}

			if ( container.tagName !== 'VIDEO' ) {
				container.appendChild( body );
			} else {
				container.parentElement.appendChild( body );
			}
		}
	}
}

/*
 * Set the height of an image relative to the width, depending on the image widht/height aspect ratio.
 *
 *
 * */

function cmplz_set_blocked_content_container_aspect_ratio( container, src, placeholder_class_index ) {
	if ( container == null ) {
		return;
	}

	// Handle image size for video
	const img = new Image();
	img.addEventListener( 'load', function() {
		const imgWidth = this.naturalWidth || 1;
		const imgHeight = this.naturalHeight;

		const w = container.clientWidth;
		const h = imgHeight * ( w / imgWidth );

		const heightCSS = src.indexOf( 'placeholder.jpg' ) === -1 ? 'height:' + h + 'px;' : '';
		cmplz_append_css( '.cmplz-placeholder-' + placeholder_class_index + ' {' + heightCSS + '}' );
	} );
	img.src = src;
}

/*
 * Keep window aspect ratio in sync when window resizes
 * To lower the number of times this code is executed, it is done with a timeout.
 *
 * */

let cmplzResizeTimer;
window.addEventListener( 'resize', function() {
	clearTimeout( cmplzResizeTimer );
	cmplzResizeTimer = setTimeout( cmplz_set_blocked_content_container, 500 );
}, true );

/*
 * 	we run this function also on an interval, because with ajax loaded content, the placeholders would otherwise not be handled.
 */
if ( Number( complianz.block_ajax_content ) === 1 ) {
	setInterval( function() {
		cmplz_set_blocked_content_container();
	}, 2000 );
}

/*
 * Enable scripts that were blocked
 *
 * */

function cmplz_enable_category( category, service ) {
	if ( Number( complianz.tm_categories ) === 1 && category !== '' ) {
		cmplz_run_tm_event( category );
	}

	const details = {};
	details.category = category;
	details.categories = cmplz_accepted_categories();
	details.region = complianz.region;
	const event = new CustomEvent( 'cmplz_before_category', { detail: details } );
	document.dispatchEvent( event );

	service = typeof service !== 'undefined' ? service : 'do_not_match';
	if ( category === '' ) {
		category = 'do_not_match';
	}

	if ( category === 'functional' ) {
		return;
	}
	//enable cookies for integrations
	if ( category === 'marketing' ) {
		cmplz_set_integrations_cookies();
	}

	//remove accept cookie notice overlay
	let selector;
	if ( service !== 'do_not_match' ) {
		selector = '.cmplz-blocked-content-notice [data-service="' + service + '"]';
	} else {
		selector = Number( complianz.clean_cookies ) !== 1 ? '.cmplz-blocked-content-notice.cmplz-accept-' + category : '.cmplz-blocked-content-notice [data-category="' + category + '"]';
	}
	document.querySelectorAll( selector ).forEach( ( obj ) => {
		const blockedElementService = obj.getAttribute( 'data-service' );
		if ( obj.parentNode.classList.contains( 'cmplz-blocked-content-notice' ) ) {
			obj = obj.parentNode;
		}
		//only remove if the service is not explicitly denied
		if ( ! cmplz_is_service_denied( blockedElementService ) ) {
			obj.parentNode.removeChild( obj );
		}
	} );

	document.querySelectorAll( '[data-category="' + category + '"], [data-service="' + service + '"]' ).forEach( ( obj ) => {
		//if a category is activated, but this specific service is denied, skip.
		const elementService = obj.getAttribute( 'data-service' );
		if ( cmplz_is_service_denied( elementService ) ) {
			return;
		}

		//if native class is included, it isn't blocked, so will have run already
		if ( obj.getAttribute( 'data-category' ) === 'functional' ) {
			return;
		}

		if ( obj.classList.contains( 'cmplz-activated' ) ) {
			return;
		}

		const tagName = obj.tagName;
		if ( tagName === 'LINK' ) {
			obj.classList.add( 'cmplz-activated' );
			const src = obj.getAttribute( 'data-href' );
			cmplz_load_css( src, category );
		} else if ( tagName === 'IMG' ) {
			obj.classList.add( 'cmplz-activated' );
			const src = obj.getAttribute( 'data-src-cmplz' );
			obj.setAttribute( 'src', src );
			//handle browser native lazy load feature
			if ( obj.getAttribute( 'data-deferlazy' ) ) {
				obj.setAttribute( 'loading', 'lazy' );
			}
			cmplz_remove_placeholder( obj );
		} else if ( tagName === 'IFRAME' ) {
			obj.classList.add( 'cmplz-activated' );
			let src = obj.getAttribute( 'data-src-cmplz' );
			const srcAttribute = obj.getAttribute( 'data-cmplz-target' ) ? obj.getAttribute( 'data-cmplz-target' ) : 'src';

			//check if there's an autoplay value we need to pass on, if it's added later on by javascript
			const autoplay = cmplz_get_url_parameter( obj.getAttribute( srcAttribute ), 'autoplay' );
			if ( autoplay === '1' ) {
				src = src + '&autoplay=1';
			}
			//handle browser native lazy load feature
			if ( obj.getAttribute( 'data-deferlazy' ) ) {
				obj.setAttribute( 'loading', 'lazy' );
			}

			obj.addEventListener( 'load', () => {
				cmplz_remove_placeholder( obj );
			} );
			obj.setAttribute( srcAttribute, src );
		} else if ( obj.classList.contains( 'cmplz-placeholder-element' ) ) {
			obj.classList.add( 'cmplz-activated' );
			//other services, no iframe, with placeholders
			//remove the added classes
			const cssIndex = obj.getAttribute( 'data-placeholder_class_index' );
			obj.classList.remove( 'cmplz-blocked-content-container', 'cmplz-placeholder-' + cssIndex );
		}
	} );

	/*
	 * Let's activate the scripts
	 */

	//create list of waiting scripts
	const scriptElements = document.querySelectorAll( 'script[data-category="' + category + '"], script[data-service="' + service + '"]' );
	scriptElements.forEach( ( obj ) => {
		const waitfor = obj.getAttribute( 'data-waitfor' );
		const src = obj.getAttribute( 'data-cmplz-src' );
		if ( waitfor ) {
			if ( src ) {
				cmplz_waiting_scripts[ waitfor ] = src;
			} else if ( obj.innerText.length > 0 ) {
				cmplz_waiting_inline_scripts[ waitfor ] = obj;
			}
		}

		//cleanup after adding it to our waiting or scriptElements list.
		if ( obj.parentElement ) {
			obj.parentElement.removeChild( obj );
		}
	} );

	//scripts: remove text/plain, and move data-src attribute to src attribute
	scriptElements.forEach( ( obj ) => {
		//we don't want already activate scripts to fire, but also we don't want scripts that weren't blocked to fire. Hence the check for type
		const script_mime_type = obj.getAttribute( 'type' );
		if ( obj.classList.contains( 'cmplz-activated' ) || ( ! script_mime_type || script_mime_type === 'text/javascript' ) ) {
			return;
		}
		obj.classList.add( 'cmplz-activated' );
		const src = obj.getAttribute( 'data-cmplz-src' );
		if ( src ) {
			obj.removeAttribute( 'type' );
			//check if this src or txt is in a waiting script. If so, skip.
			if ( cmplz_is_waiting_script( cmplz_waiting_scripts, src ) ) {
				return;
			}

			if ( obj.getAttribute( 'data-post_scribe_id' ) ) {
				const psID = '#' + obj.getAttribute( 'data-post_scribe_id' );
				const postScribeObj = document.querySelector( psID );
				if ( postScribeObj ) {
					postScribeObj.innerHtml( '' );
					postscribe( psID, '<script src=' + src + '></script>' );
				}
			} else {
				cmplz_run_script( src, category, service, 'src', obj );
			}
		} else if ( obj.innerText.length > 0 ) {
			//check if this src or txt is in a waiting script. If so, skip.
			if ( cmplz_is_waiting_script( cmplz_waiting_inline_scripts, obj.innerText ) ) {
				return;
			}

			cmplz_run_script( obj.innerText, category, service, 'inline', obj );
		}
	} );

	// if there are no blockable scripts at all, we still want to provide a hook
	// in most cases, there are blocked scripts, and run_after_all_scripts hook won't be fired yet. In that
	// case only the category event is fired (if activated), and this function will run
	// again from the script activation callbacks.
	cmplz_run_after_all_scripts( category, service );
}

/*
 * remove added classes from the blocked content container
 *
 * @param obj
 */
function cmplz_remove_placeholder( obj ) {
	const blocked_content_container = obj.closest( '.cmplz-blocked-content-container' );
	if ( blocked_content_container ) {
		const cssIndex = blocked_content_container.getAttribute( 'data-placeholder_class_index' );
		blocked_content_container.classList.remove( 'cmplz-blocked-content-container', 'cmplz-placeholder-' + cssIndex );
	}
	obj.classList.remove( 'cmplz-iframe-styles', 'cmplz-iframe', 'video-wrap' );
}

/*
 * check if the passed source has a waiting script that should be executed, and return it if so.
 * @param waiting_scripts
 * @param src
 * @returns {*}
 */

function cmplz_get_waiting_script( waiting_scripts, src ) {
	for ( const waitfor in waiting_scripts ) {
		if ( waiting_scripts.hasOwnProperty( waitfor ) ) {
			if ( src.indexOf( waitfor ) !== -1 ) {
				const output = waiting_scripts[ waitfor ];
				delete waiting_scripts[ waitfor ];
				return output;
			}
		}
	}
	return false;
}

/*
 * Because we need a key=>value array in javascript, the .length check for an empty array doesn't work.
 * @param arr
 * @returns {boolean}
 */

function cmplz_array_is_empty( arr ) {
	for ( const key in arr ) {
		if ( arr.hasOwnProperty( key ) ) {
			return false;
		}
	}
	return true;
}

/*
 * Check if the passed src or script is waiting for another script and should not execute
 * @param waiting_scripts
 * @param srcOrScript
 */

function cmplz_is_waiting_script( waiting_scripts, srcOrScript ) {
	for ( const waitfor in waiting_scripts ) {
		if ( waiting_scripts.hasOwnProperty( waitfor ) ) {
			let waitingScript = waiting_scripts[ waitfor ];
			if ( typeof waitingScript !== 'string' ) {
				waitingScript = waitingScript.innerText;
			}

			if ( srcOrScript.indexOf( waitingScript ) !== -1 || waitingScript.indexOf( srcOrScript ) !== -1 ) {
				return true;
			}
		}
	}
	return false;
}

/*
 * if all scripts have been executed, fire a hook.
 */

function cmplz_run_after_all_scripts( category, service ) {
	//fire an event so custom scripts can hook into this.
	const enableService = service !== 'do_not_match' && ! cmplz_in_array( service, cmplz_fired_service_events );
	const enableCategory = category !== 'do_not_match' && ! cmplz_in_array( category, cmplz_fired_category_events );
	if ( enableCategory || enableService ) {
		if ( enableCategory ) {
			cmplz_fired_category_events.push( category );
		}
		if ( enableService ) {
			cmplz_fired_service_events.push( service );
		}
		const details = {};
		details.category = category;
		details.service = service;
		details.categories = cmplz_accepted_categories();
		details.services = cmplz_get_all_service_consents();
		details.region = complianz.region;

		const event = new CustomEvent( 'cmplz_enable_category', { detail: details } );
		document.dispatchEvent( event );
	}

	if ( ! cmplz_all_scripts_hook_fired && cmplz_array_is_empty( cmplz_waiting_inline_scripts ) && cmplz_array_is_empty( cmplz_waiting_scripts ) ) {
		const event = new CustomEvent( 'cmplz_run_after_all_scripts', { detail: category, service } );
		document.dispatchEvent( event );
		cmplz_all_scripts_hook_fired = true;
	}
}

/*
 * Fire an event in Tag Manager
 *
 *
 * */

const cmplz_fired_events = [];
function cmplz_run_tm_event( category ) {
	if ( cmplz_fired_events.indexOf( category ) === -1 ) {
		cmplz_fired_events.push( category );
		window.dataLayer = window.dataLayer || [];
		window.dataLayer.push( {
			event: 'cmplz_event_' + category,
		} );
		const event = new CustomEvent( 'cmplz_tag_manager_event', { detail: category } );
		document.dispatchEvent( event );
	}
}

/*
 * Accept all categories
 */
window.cmplz_accept_all = function() {
	cmplz_clear_all_service_consents();
	cmplz_fire_before_categories_consent( cmplz_categories );

	for ( const key in cmplz_categories ) {
		if ( cmplz_categories.hasOwnProperty( key ) ) {
			cmplz_set_consent( cmplz_categories[ key ], 'allow' );
		}
	}
	cmplz_sync_category_checkboxes();
};

function cmplz_fire_before_categories_consent( categories ) {
	const details = {};
	details.categories = categories;
	details.region = complianz.region;
	const event = new CustomEvent( 'cmplz_before_categories_consent', { detail: details } );
	document.dispatchEvent( event );
}

/*
 * Deny all categories, and reload if needed.
 */
window.cmplz_deny_all = function() {
	for ( const key in cmplz_categories ) {
		if ( cmplz_categories.hasOwnProperty( key ) ) {
			cmplz_set_consent( cmplz_categories[ key ], 'deny' );
		}
	}
	const consentLevel = cmplz_highest_accepted_category();
	let reload = false;

	if ( consentLevel !== 'functional' || cmplz_exists_service_consent() ) {
		reload = true;
	}
	if ( cmplz_clear_cookies( 'cmplz_service' ) ) {
		reload = true;
	}

	//has to be after the check if should be reloaded, otherwise that check won't work.
	cmplz_clear_all_service_consents();
	cmplz_integrations_revoke();
	cmplz_fire_categories_event();
	cmplz_track_status();

	const event = new CustomEvent( 'cmplz_revoke', { detail: reload } );
	document.dispatchEvent( event );

	//we need to let the iab extension handle the reload, otherwise the consent revoke might not be ready yet.
	if ( ! complianz.tcf_active && reload ) {
		cmplz_reload_browser_compatible();
	}
};

/*
 * If current cookie policy has changed, reset cookie consent
 *
 * */

function cmplz_check_cookie_policy_id() {
	const user_policy_id = cmplz_get_cookie( 'policy_id' );
	if ( user_policy_id && ( parseInt( complianz.current_policy_id ) !== parseInt( user_policy_id ) ) ) {
		cmplz_deny_all();
		cmplz_set_banner_status( 'show' );
		cmplz_clear_cookies( 'cmplz' );
	}
}

window.conditionally_show_banner = function() {
	//merge userdata with complianz data, in case a b testing is used with user specific cookie banner data
	//objects are merged so user_data will override data in complianz object
	complianz = cmplz_merge_object( complianz, cmplz_user_data );
	//check if we need to redirect to another legal document, for a specific region
	cmplz_maybe_auto_redirect();
	cmplz_set_blocked_content_container();

	/*
	 * Integration with WordPress, tell what kind of consent type we're using, then fire an event
	 */

	window.wp_consent_type = complianz.consenttype;
	let event = new CustomEvent( 'wp_consent_type_defined' );
	document.dispatchEvent( event );
	event = new CustomEvent( 'cmplz_before_cookiebanner' );
	document.dispatchEvent( event );
	if ( Number( complianz.forceEnableStats ) === 1 && complianz.consenttype === 'optin' ) {
		cmplz_set_consent( 'statistics', 'allow' );
	}
	const rev_cats = cmplz_categories.reverse();
	const consented_categories = [];
	for ( const key in rev_cats ) {
		if ( rev_cats.hasOwnProperty( key ) ) {
			const category = cmplz_categories[ key ];
			if ( cmplz_has_consent( category ) ) {
				consented_categories.push( category );
			}
		}
	}
	cmplz_fire_before_categories_consent( consented_categories );
	for ( const key in consented_categories ) {
		if ( rev_cats.hasOwnProperty( key ) ) {
			const category = consented_categories[ key ];
			cmplz_enable_category( category );
		}
	}

	if ( cmplz_exists_service_consent() ) {
		//if any service is enabled, allow the general services also, because some services are partially 'general'
		cmplz_enable_category( '', 'general' );
		const services = cmplz_get_services_on_page();
		for ( const key in services ) {
			if ( services.hasOwnProperty( key ) ) {
				const service = services[ key ].service;
				const category = services[ key ].category;
				if ( cmplz_has_service_consent( service, category ) ) {
					document.querySelectorAll( '.cmplz-accept-service[data-service="' + service + '"]' ).forEach( ( obj ) => {
						obj.checked = true;
					} );
					cmplz_enable_category( '', service );
				}
			}
		}
	}

	cmplz_sync_category_checkboxes();
	cmplz_integrations_init();
	cmplz_check_cookie_policy_id();
	cmplz_set_up_auto_dismiss();

	//if we're on the cookie policy page, we dynamically load the applicable revoke checkbox
	cmplz_load_manage_consent_container();

	event = new CustomEvent( 'cmplz_cookie_banner_data', { detail: complianz } );
	document.dispatchEvent( event );

	//if no categories were saved before, we do it now
	if ( cmplz_get_cookie( 'saved_categories' ) === '' ) {
		//for Non optin/optout visitors, and DNT users, we just track the no-warning option
		if ( complianz.consenttype !== 'optin' && complianz.consenttype !== 'optout' ) {
			cmplz_track_status();
		} else if ( cmplz_do_not_track() ) {
			cmplz_track_status( 'do_not_track' );
		}
	}

	cmplz_set_category_as_body_class();
	//fire cats event, but do not fire a track, as we do this on exit.
	cmplz_fire_categories_event();
	if ( ! cmplz_do_not_track() ) {
		if ( complianz.consenttype === 'optin' ) {
			if ( complianz.forceEnableStats ) {
				cmplz_enable_category( 'statistics' );
			}
			cmplz_log( 'opt-in' );
			show_cookie_banner();
		} else if ( complianz.consenttype === 'optout' ) {
			cmplz_log( 'opt-out' );
			show_cookie_banner();
		} else {
			cmplz_log( 'other consent type, no cookie warning' );
			//on other consent types, all scripts are enabled by default.
			cmplz_accept_all();
		}
	} else {
		cmplz_log( 'global privacy control or do not track detected: no banner.' );
		cmplz_track_status( 'do_not_track' );
	}
};

/**
 * Check if User has set either GPC or DNT in the browser.
 * @return {boolean} True if do not track is enabled and GPC or DNT is set.
 */
function cmplz_do_not_track() {
	const dnt = 'doNotTrack' in navigator && navigator.doNotTrack === '1';
	const gpc = 'globalPrivacyControl' in navigator && navigator.globalPrivacyControl;
	return !! ( complianz.do_not_track_enabled && ( gpc || dnt ) );
}

/*
 * Get list of services active on the page
 * @returns {*[]}
 */
function cmplz_get_services_on_page() {
	const services = [];
	document.querySelectorAll( '[data-service]' ).forEach( ( obj ) => {
		const service = obj.getAttribute( 'data-service' );
		const category = obj.getAttribute( 'data-category' );
		if ( services.indexOf( service ) === -1 ) {
			services.push( {
				category,
				service,
			} );
		}
	} );
	return services;
}

/*
 * Run the actual cookie warning
 *
 * */

window.show_cookie_banner = function() {
	let disableCookiebanner = complianz.disable_cookiebanner || cmplz_is_speedbot();
	//do not show banner when manage consent area on cookie policy is visible
	//when users use only the shortcode, the manage consent container is not active, but the dropdown cookie policy class is.
	//when the complianz shortcode is used, the dropdown cookie policy class is loaded late because it's loaded with javascript.
	let tmpDismissCookiebanner = false;
	if ( document.querySelector( '#cmplz-manage-consent-container' ) || document.querySelector( '.cmplz-dropdown-cookiepolicy' ) ) {
		tmpDismissCookiebanner = true;
	}

	const container = document.getElementById( 'cmplz-cookiebanner-container' );
	if ( container ) {
		document.body.prepend( container );
	}

	const pageLinks = complianz.page_links[ complianz.region ];
	//get correct banner, based on banner_id
	cmplz_banner = document.querySelector( '.cmplz-cookiebanner.banner-' + complianz.user_banner_id + '.' + complianz.consenttype );
	if ( ! cmplz_banner ) {
		disableCookiebanner = true;
	}
	cmplz_manage_consent_button = document.querySelector( '#cmplz-manage-consent .cmplz-manage-consent.manage-consent-' + complianz.user_banner_id );

	// Reveal the banner once its stylesheet is applied. Idempotent: removing an
	// already-removed class is a no-op, so calling this more than once is safe.
	const revealBanner = function() {
		if ( disableCookiebanner ) {
			return;
		}
		cmplz_banner.classList.remove( 'cmplz-hidden' );
		cmplz_manage_consent_button.classList.remove( 'cmplz-hidden' );

		// Activate focus trap only if banner is currently shown (not already dismissed)
		if ( cmplz_get_banner_status() === 'show' ) {
			cmplz_trap_focus();

			// Set focus to the close button when banner is shown
			const closeButton = cmplz_banner.querySelector( '.cmplz-close' );
			if ( closeButton ) {
				closeButton.focus();
			}
		}
	};

	// Guarantee the banner stylesheet is present and enabled, then reveal.
	// In the deterministic case the sheet was enqueued server-side and is
	// already applied, so we reveal immediately (no FOUC). Otherwise the sheet
	// is injected here and we reveal on load, with a timeout safety net for the
	// race where the sheet finished loading before the listener attached.
	const bannerLink = cmplzEnsureBannerCss();
	if ( ! disableCookiebanner ) {
		if ( bannerLink && bannerLink.sheet ) {
			revealBanner();
		} else if ( bannerLink ) {
			bannerLink.addEventListener( 'load', revealBanner );
			setTimeout( revealBanner, 500 );
		} else {
			revealBanner();
		}
	}
	if ( cmplz_banner && ! disableCookiebanner ) {
		cmplz_banner.querySelectorAll( '.cmplz-links a:not(.cmplz-external), .cmplz-buttons a:not(.cmplz-external)' ).forEach( ( obj ) => {
			const docElement = obj;
			docElement.classList.add( 'cmplz-hidden' );
			for ( const pageType in pageLinks ) {
				if ( pageLinks.hasOwnProperty( pageType ) && docElement.classList.contains( pageType ) ) {
					docElement.setAttribute( 'href', pageLinks[ pageType ].url + docElement.getAttribute( 'data-relative_url' ) );
					if ( docElement.innerText === '{title}' ) {
						docElement.innerText = cmplz_html_decode( pageLinks[ pageType ].title );
					}
					docElement.classList.remove( 'cmplz-hidden' );
				}
			}
		} );

		cmplz_set_banner_status();
		//we don't use the setBannerStatus function here, as we don't want to save it in a cookie now.
		if ( tmpDismissCookiebanner ) {
			cmplz_banner.classList.remove( 'cmplz-show' );
			cmplz_banner.classList.add( 'cmplz-dismissed' );
			cmplz_manage_consent_button.classList.remove( 'cmplz-dismissed' );
			cmplz_manage_consent_button.classList.add( 'cmplz-show' );
		}
	}

	const event = new CustomEvent( 'cmplz_cookie_warning_loaded', { detail: complianz.region } );
	document.dispatchEvent( event );
};

/*
 * Guarantee the banner stylesheet for the current variant is present in the DOM
 * and enabled. Idempotent — safe to call on first load and after every
 * client-side (Interactivity API) navigation.
 *
 * From WP 6.9 the iAPI router disables any stylesheet absent from the fetched
 * server HTML by setting CSSStyleSheet.disabled = true (it never removes the
 * element). When the sheet was enqueued server-side (deterministic single
 * variant) it survives by design; if the router disabled it we re-enable it
 * synchronously — zero network, zero flash. When the sheet was injected by JS
 * (A/B or multiple consent types) we re-enable it, or inject it if missing.
 *
 * @returns {HTMLLinkElement|null} the banner <link>, or null if none resolved.
 */
function cmplzEnsureBannerCss() {
	//server-enqueued sheet carries the WordPress style handle id.
	//Invariant: enqueue_banner_css() only enqueues server-side in the deterministic
	//case (no A/B testing), where user_banner_id resolves to the default banner id
	//it enqueued under — so this id matches. Under A/B testing the variant differs
	//per user, nothing is enqueued server-side, and we fall through to the injected/
	//inject paths below instead.
	const handleId = 'cmplz-banner-' + complianz.user_banner_id + '-' + complianz.consenttype + '-css';
	const enqueued = document.getElementById( handleId );
	if ( enqueued && enqueued.rel === 'stylesheet' ) {
		if ( enqueued.sheet && enqueued.sheet.disabled ) {
			enqueued.sheet.disabled = false;
		}
		return enqueued;
	}

	const css_file_url = complianz.css_file.replace( '{type}', complianz.consenttype ).replace( '{banner_id}', complianz.user_banner_id );
	if ( complianz.css_file.indexOf( 'cookiebanner/css/defaults/banner' ) !== -1 ) {
		cmplz_log( 'Fallback default css file used. Please re-save banner settings, or check file writing permissions in uploads directory' );
	}

	//match an already-injected <link> by filename, ignoring cache-busting query strings
	const filename = css_file_url.split( '?' )[ 0 ].split( '/' ).pop();
	let injected = null;
	document.querySelectorAll( 'link[rel="stylesheet"]' ).forEach( function( el ) {
		if ( el.href.split( '?' )[ 0 ].split( '/' ).pop() === filename ) {
			injected = el;
		}
	} );
	if ( injected ) {
		if ( injected.sheet && injected.sheet.disabled ) {
			injected.sheet.disabled = false;
		}
		return injected;
	}

	//nothing present yet — inject it (multi-variant first load, or default-CSS fallback)
	const link = document.createElement( 'link' );
	link.href = css_file_url;
	link.type = 'text/css';
	link.rel = 'stylesheet';
	document.getElementsByTagName( 'head' )[ 0 ].appendChild( link );
	return link;
}

/*
 * Re-sync consent state and de-duplicate banner instances after a client-side
 * navigation. The banner is rendered once in the footer (outside any router
 * region) so it normally persists untouched; this is a safety pass in case a
 * theme/plugin wraps it in a router region, which would duplicate it with the
 * cache-default markup on swap.
 */
function cmplzSyncConsentState() {
	const banners = document.querySelectorAll( '.cmplz-cookiebanner' );
	//drop copies brought in inside a router region; the footer original persists
	if ( banners.length > 1 ) {
		banners.forEach( function( banner ) {
			if ( banner.closest( '[data-wp-router-region]' ) ) {
				banner.remove();
			}
		} );
	}

	//re-apply visibility from the stored consent status
	const dismissed = cmplz_get_banner_status() === 'dismissed';
	document.querySelectorAll( '.cmplz-cookiebanner' ).forEach( function( banner ) {
		banner.classList.toggle( 'cmplz-show', ! dismissed );
		banner.classList.toggle( 'cmplz-dismissed', dismissed );
	} );

	//remove a manage-consent button duplicated inside a region, but only when an
	//out-of-region original survives. Mirrors the banner guard above: if a theme
	//wraps the whole page (footer included) in a single router region, the only
	//copy lives inside it — removing it unconditionally would delete the manage-
	//consent button with no replacement.
	const manageBtns = document.querySelectorAll( '#cmplz-manage-consent' );
	const hasOriginal = Array.prototype.some.call( manageBtns, function( btn ) {
		return ! btn.closest( '[data-wp-router-region]' );
	} );
	if ( hasOriginal ) {
		manageBtns.forEach( function( btn ) {
			if ( btn.closest( '[data-wp-router-region]' ) ) {
				btn.remove();
			}
		} );
	}
}

/*
 * Arm the client-side navigation hook. On WP 7.0+ the companion script module
 * (complianz-router.js) drives navigation via the official watch()+state.url
 * API and notifies us with a DOM event. On WP 6.9 we fall back to a History API
 * patch, scoped to pages that actually have an iAPI router region. Handlers are
 * idempotent, so duplicate/extra ticks are harmless.
 */
function cmplzInitNavigationHook() {
	if ( ! document.querySelector( '[data-wp-router-region]' ) ) {
		return; //no iAPI navigation on this page
	}

	const onNavigate = function() {
		cmplzEnsureBannerCss();
		cmplzSyncConsentState();
	};

	if ( complianz.iapi_nav === 'module' ) {
		//WP 7.0+: the companion module drives navigation via watch()
		document.addEventListener( 'cmplz_router_navigated', onNavigate );
		return;
	}

	//WP 6.9 fallback: patch the History API + popstate.
	//Defer onNavigate so it never runs synchronously inside the iAPI router's
	//pushState call — a synchronous DOM mutation there triggers the iAPI
	//MutationObserver, which re-evaluates directives that access private stores
	//and throws "Cannot unlock a private store with an invalid lock code".
	const onNavigateDeferred = function() {
		requestAnimationFrame( function() {
			onNavigate();
			setTimeout( onNavigate, 150 );
		} );
	};
	[ 'pushState', 'replaceState' ].forEach( function( method ) {
		const original = history[ method ];
		history[ method ] = function() {
			const result = original.apply( this, arguments );
			onNavigateDeferred();
			return result;
		};
	} );
	window.addEventListener( 'popstate', onNavigateDeferred );
}

/*
 * Get the status of the banner: dismissed | show
 * @returns {string}
 */
window.cmplz_get_banner_status = function() {
	return cmplz_get_cookie( 'banner-status' );
};

/*
 * Set the banner status so it will be either shown or dismissed, and store it in a cookie.
 * @param status (optional)
 */

window.cmplz_set_banner_status = function( status ) {
	let prevStatus = cmplz_get_cookie( 'banner-status' );
	status = typeof status !== 'undefined' ? status : prevStatus;
	if ( status !== prevStatus ) {
		cmplz_set_cookie( 'banner-status', status );
	}
	if ( status.length === 0 ) {
		status = 'show';
	}

	if ( status === 'show' ) {
		prevStatus = 'dismissed';
	} else {
		prevStatus = 'show';
	}

	if ( cmplz_banner && status.length > 0 ) {
		cmplz_banner.classList.remove( 'cmplz-' + prevStatus );
		cmplz_banner.classList.add( 'cmplz-' + status );
		if ( cmplz_manage_consent_button ) {
			cmplz_manage_consent_button.classList.add( 'cmplz-' + prevStatus );
			cmplz_manage_consent_button.classList.remove( 'cmplz-' + status );
		}
	}

	if ( cmplz_banner_container && complianz.soft_cookiewall ) {
		cmplz_banner_container.classList.remove( 'cmplz-' + prevStatus );
		cmplz_banner_container.classList.add( 'cmplz-' + status, 'cmplz-soft-cookiewall' );
	}
	const event = new CustomEvent( 'cmplz_banner_status', { detail: status } );
	document.dispatchEvent( event );
	cmplz_start_clean();

	if ( status === 'show' ) {
		// Activate focus trap only if soft cookie wall is enabled
		cmplz_trap_focus();

		// Set focus to the close button when banner is shown
		const closeButton = cmplz_banner.querySelector( '.cmplz-close' );
		if ( closeButton ) {
			closeButton.focus();
		}
	} else {
		// Release focus trap when banner is dismissed
		cmplz_release_focus_trap();
	}
};

/*
 * Check if current visitor is a bot
 *
 * @returns {boolean}
 */
function cmplz_is_bot() {
	const botPattern = '(googlebot\/|Googlebot-Mobile|Google-InspectionTool|Googlebot-Image|Google favicon|Mediapartners-Google|bingbot|slurp|java|wget|curl|Commons-HttpClient|Python-urllib|libwww|httpunit|nutch|phpcrawl|msnbot|jyxobot|FAST-WebCrawler|FAST Enterprise Crawler|biglotron|teoma|convera|seekbot|gigablast|exabot|ngbot|ia_archiver|GingerCrawler|webmon |httrack|webcrawler|grub.org|UsineNouvelleCrawler|antibot|netresearchserver|speedy|fluffy|bibnum.bnf|findlink|msrbot|panscient|yacybot|AISearchBot|IOI|ips-agent|tagoobot|MJ12bot|dotbot|woriobot|yanga|buzzbot|mlbot|yandexbot|purebot|Linguee Bot|Voyager|CyberPatrol|voilabot|baiduspider|citeseerxbot|spbot|twengabot|postrank|turnitinbot|scribdbot|page2rss|sitebot|linkdex|Adidxbot|blekkobot|ezooms|dotbot|Mail.RU_Bot|discobot|heritrix|findthatfile|europarchive.org|NerdByNature.Bot|sistrix crawler|ahrefsbot|Aboundex|domaincrawler|wbsearchbot|summify|ccbot|edisterbot|seznambot|ec2linkfinder|gslfbot|aihitbot|intelium_bot|facebookexternalhit|yeti|RetrevoPageAnalyzer|lb-spider|sogou|lssbot|careerbot|wotbox|wocbot|ichiro|DuckDuckBot|lssrocketcrawler|drupact|webcompanycrawler|acoonbot|openindexspider|gnam gnam spider|web-archive-net.com.bot|backlinkcrawler|coccoc|integromedb|content crawler spider|toplistbot|seokicks-robot|it2media-domain-crawler|ip-web-crawler.com|siteexplorer.info|elisabot|proximic|changedetection|blexbot|arabot|WeSEE:Search|niki-bot|CrystalSemanticsBot|rogerbot|360Spider|psbot|InterfaxScanBot|Lipperhey SEO Service|CC Metadata Scaper|g00g1e.net|GrapeshotCrawler|urlappendbot|brainobot|fr-crawler|binlar|SimpleCrawler|Livelapbot|Twitterbot|cXensebot|smtbot|bnf.fr_bot|A6-Indexer|ADmantX|Facebot|Twitterbot|OrangeBot|memorybot|AdvBot|MegaIndex|SemanticScholarBot|ltx71|nerdybot|xovibot|BUbiNG|Qwantify|archive.org_bot|Applebot|TweetmemeBot|crawler4j|findxbot|SemrushBot|yoozBot|lipperhey|y!j-asr|Domain Re-Animator Bot|AddThis)';
	const reBot = new RegExp( botPattern, 'i' );
	const userAgent = navigator.userAgent;
	return reBot.test( userAgent );
}
/*
 * Check if current visitor is a speedbot
 *
 * @returns {boolean}
 */
function cmplz_is_speedbot() {
	const userAgent = navigator.userAgent;
	const speedBotPattern = '(GTmetrix|pingdom|pingbot|Lighthouse)';
	const speedBot = new RegExp( speedBotPattern, 'i' );
	return speedBot.test( userAgent );
}

/*
 * Check if there is consent for a category or service
 * @param category
 * @returns {boolean}
 */

window.cmplz_has_consent = function( category ) {
	if ( cmplz_is_bot() ) {
		return true;
	}

	if ( category === 'functional' ) {
		return true;
	}
	let has_consent, value;

	//if DNT is detected, we should only return the actual cookie value, and not look at the consenttype
	if ( cmplz_do_not_track() ) {
		value = cmplz_get_cookie( category );
		has_consent = ( value === 'allow' );
		return has_consent;
	}

	/*
	 * categories
	 */
	value = cmplz_get_cookie( category );
	if ( ( complianz.consenttype === 'optout' || complianz.consenttype === 'other' ) && value === '' ) {
		//if it's opt out and no cookie is set we should return true
		has_consent = true;
	} else {
		//all other situations, return only true if value is allow
		has_consent = ( value === 'allow' );
	}

	return has_consent;
};

/*
 * Check if a service has consent
 * @param service
 * @returns {boolean|*}
 */
window.cmplz_is_service_denied = function( service ) {
	//Check if it's in the consented services cookie
	const consented_services_json = cmplz_get_cookie( 'consented_services' );
	let consented_services;
	try {
		consented_services = JSON.parse( consented_services_json );
	} catch ( e ) {
		consented_services = {};
	}

	if ( ! consented_services.hasOwnProperty( service ) ) {
		return false;
	}
	return ! consented_services[ service ];
};

/*
 * Check if a service has consent
 * @param service
 * @param category
 * @returns {boolean|*}
 */
window.cmplz_has_service_consent = function( service, category ) {
	//Check if it's in the consented services cookie
	const consented_services_json = cmplz_get_cookie( 'consented_services' );
	let consented_services;
	try {
		consented_services = JSON.parse( consented_services_json );
	} catch ( e ) {
		consented_services = {};
	}

	if ( ! consented_services.hasOwnProperty( service ) ) {
		//default to the category
		return cmplz_has_consent( category );
	}
	return consented_services[ service ];
};

/*
 * check if there's at least one service with consent
 * @returns {boolean}
 */
function cmplz_exists_service_consent() {
	const consented_services_json = cmplz_get_cookie( 'consented_services' );
	let consented_services;
	try {
		consented_services = JSON.parse( consented_services_json );
		for ( const key in consented_services ) {
			if ( consented_services.hasOwnProperty( key ) ) {
				if ( consented_services[ key ] === true ) {
					return true;
				}
			}
		}
	} catch ( e ) {
		return false;
	}
	return false;
}

/*
 * Set consent for a service
 * @param service
 * @param consented
 */
function cmplz_set_service_consent( service, consented ) {
	const consented_services_json = cmplz_get_cookie( 'consented_services' );
	let consented_services;
	try {
		consented_services = JSON.parse( consented_services_json );
	} catch ( e ) {
		consented_services = {};
	}
	consented_services[ service ] = consented;
	cmplz_set_cookie( 'consented_services', JSON.stringify( consented_services ) );
	const details = {};
	details.service = service;
	details.value = consented;
	details.region = complianz.region;
	//when the status is changed, we may need to fire scripts again, so we reset it.
	cmplz_all_scripts_hook_fired = false;
	const event = new CustomEvent( 'cmplz_status_change_service', { detail: details } );
	document.dispatchEvent( event );
}

/*
 * Remove all service consents
 */
function cmplz_clear_all_service_consents() {
	cmplz_set_cookie( 'consented_services', '' );
}

/*
 * Get all consented or denied services
 */

function cmplz_get_all_service_consents() {
	const consented_services_json = cmplz_get_cookie( 'consented_services' );
	let consented_services;
	try {
		consented_services = JSON.parse( consented_services_json );
	} catch ( e ) {
		consented_services = {};
	}
	return consented_services;
}
/*
 * Get cookie path
 * @returns {*}
 */
function cmplz_get_cookie_path() {
	return typeof complianz.cookie_path !== 'undefined' && complianz.cookie_path !== '' ? complianz.cookie_path : '/';
}

/*
 * retrieve domain to set the cookies on
 * @returns {string}
 */
function cmplz_get_cookie_domain() {
	if ( Number( complianz.set_cookies_on_root ) === 1 && complianz.cookie_domain.length > 3 && ! complianz.cookie_domain.includes( 'localhost' ) ) {
		return complianz.cookie_domain;
	}
	return '';
}

/*
 * Set consent for a category
 * @param category
 * @param value
 */
window.cmplz_set_consent = function( category, value ) {
	cmplz_set_accepted_cookie_policy_id();

	//functional is always allow
	value = category === 'functional' ? 'allow' : value;
	const previous_value = cmplz_get_cookie( category );

	//keep checkboxes in banner and on cookie policy in sync
	//do this before the change check to ensure sync: https://github.com/Really-Simple-Plugins/complianz-gdpr/issues/324
	const checked = value === 'allow';
	document.querySelectorAll( 'input.cmplz-' + category ).forEach( ( obj ) => {
		obj.checked = checked;
	} );

	//do not trigger a change event if nothing has changed.
	if ( previous_value === value ) {
		return;
	}

	cmplz_set_cookie( category, value );
	if ( value === 'allow' ) {
		cmplz_enable_category( category );
	}

	cmplz_wp_set_consent( category, value );
	if ( category === 'statistics' ) {
		cmplz_wp_set_consent( 'statistics-anonymous', 'allow' );
	}

	const details = new Object();
	details.category = category;
	details.value = value;
	details.region = complianz.region;
	details.categories = cmplz_accepted_categories();
	//when the status is changed, we may need to fire scripts again, so we reset it.
	cmplz_all_scripts_hook_fired = false;
	const event = new CustomEvent( 'cmplz_status_change', { detail: details } );
	document.dispatchEvent( event );
	if ( category === 'marketing' && value === 'deny' && previous_value === 'allow' ) {
		cmplz_integrations_revoke();
		//give the code some time to finish, so our track status code can send a signal to the backend.
		setTimeout( function() {
			cmplz_reload_browser_compatible();
		}, 500 );
	}
};

/*
* In some browsers, like firefox, the reload does not force reload, but keeps cached data, causing e.g. Google Maps to load anyway.
*/
function cmplz_reload_browser_compatible() {
	if ( navigator.userAgent.toLowerCase().indexOf( 'firefox' ) > -1 ) {
		const url = new URL( window.location.href );
		url.searchParams.set( 'cmplz-force-reload', Date.now().toString() );
		window.location.href = url.toString();
	} else {
		window.location.reload();
	}
}

/*
 * We use ajax to check the consenttype based on region, otherwise caching could prevent the user specific warning
 *
 * */

let cmplz_user_data = [];
//check if it's already stored
if ( typeof ( Storage ) !== 'undefined' && sessionStorage.cmplz_user_data ) {
	cmplz_user_data = JSON.parse( sessionStorage.cmplz_user_data );
}

//if not stored yet, load. As features in the user object can be changed on updates, we also check for the version
if ( Number( complianz.geoip ) === 1 && ( cmplz_user_data.length === 0 || ( cmplz_user_data.version !== complianz.version ) || ( cmplz_user_data.banner_version !== complianz.banner_version ) ) ) {
	const request = new XMLHttpRequest();
	let cmplzUserRegion = cmplz_get_url_parameter( window.location.href, 'cmplz_user_region' );
	cmplzUserRegion = cmplzUserRegion ? '&cmplz_user_region=' + cmplzUserRegion : '';
	request.open( 'GET', complianz.url + 'banner?' + complianz.locale + cmplzUserRegion, true );
	request.setRequestHeader( 'Content-type', 'application/json' );
	request.send();
	request.onload = function() {
		cmplz_user_data = JSON.parse( request.response );
		sessionStorage.cmplz_user_data = JSON.stringify( cmplz_user_data );
		conditionally_show_banner();
	};
} else {
	conditionally_show_banner();
}

//keep the banner styled + consistent across Interactivity API client-side navigation
cmplzInitNavigationHook();

/*
 *  when ab testing, or using records of consent, we want to keep track of the unique user id
 */

if ( Number( complianz.store_consent ) === 1 ) {
	const cmplz_id_cookie = cmplz_get_cookie( 'id' );
	let cmplz_id_session = '';
	let cmplz_id = '';
	if ( typeof ( Storage ) !== 'undefined' && sessionStorage.cmplz_id ) {
		cmplz_id_session = JSON.parse( sessionStorage.cmplz_id );
	}

	if ( cmplz_id_cookie.length === 0 && cmplz_id_session.length > 0 ) {
		cmplz_id = cmplz_id_session;
		cmplz_set_cookie( 'id', cmplz_id );
	}

	if ( cmplz_id_cookie.length > 0 && cmplz_id_session.length === 0 ) {
		cmplz_id = cmplz_id_cookie;
	}

	if ( typeof ( Storage ) !== 'undefined' ) {
		sessionStorage.cmplz_id = JSON.stringify( cmplz_id );
	}
}

// visibilitychange and pagehide work in more browsers hence we check if they are supported and try to use them
document.addEventListener( 'visibilitychange', function() {
	if ( document.visibilityState === 'hidden' ) {
		cmplz_track_status_end();
	}
} );
window.addEventListener( 'pagehide', cmplz_track_status_end, false );
window.addEventListener( 'beforeunload', cmplz_track_status_end, false );

function cmplz_track_status_end() {
	if ( ! cmplz_consent_stored_once ) {
		cmplz_track_status();
	}
}

/*
 * This creates an API which devs can use to trigger actions in complianz.
 */
document.addEventListener( 'cmplz_consent_action', function( e ) {
	cmplz_set_consent( e.detail.category, 'allow' );
	cmplz_fire_categories_event();
	cmplz_track_status();
} );

/*
 * For both opt-in and opt-out, clicking cmplz-accept should result in accepting all categories
 */
cmplz_add_event( 'click', '.cmplz-accept', function( e ) {
	e.preventDefault();
	setTimeout( () => {
		cmplz_accept_all();
		cmplz_set_banner_status( 'dismissed' );
		cmplz_fire_categories_event();
		cmplz_track_status();
	}, 0 );
} );

/*
 *  Accept marketing cookies by clicking any other link cookie acceptance from a custom link
 */

//cmplz-accept-marketing is deprecated
cmplz_add_event( 'click', '.cmplz-accept-category, .cmplz-accept-marketing', function( e ) {
	e.preventDefault();
	const obj = e.target;
	const service = obj.getAttribute( 'data-service' );
	let category = obj.getAttribute( 'data-category' );
	category = category ? category : 'marketing';
	if ( Number( complianz.clean_cookies ) === 1 && typeof service !== 'undefined' && service ) {
		cmplz_set_service_consent( service, true );
		cmplz_enable_category( '', 'general' );
		cmplz_enable_category( '', service );
	} else {
		//we're activating a category, so do not need service activation here.
		// cmplz_enable_category('', 'general');
		cmplz_set_consent( category, 'allow' );
	}
	cmplz_set_banner_status( 'dismissed' );
	cmplz_fire_categories_event();
	cmplz_track_status();
} );

/*
 * Accept a specific service
 */
cmplz_add_event( 'click', '.cmplz-accept-service', function( e ) {
	const obj = e.target;
	//INPUT should be handled by change event, these are checkboxes on the cookie policy
	const tagName = obj.tagName;
	if ( tagName === 'INPUT' ) {
		return;
	}
	const service = obj.getAttribute( 'data-service' );
	if ( typeof service !== 'undefined' ) {
		cmplz_set_service_consent( service, true );
		cmplz_enable_category( '', 'general' );
		cmplz_enable_category( '', service );
	}
	cmplz_fire_categories_event();
	cmplz_track_status();
} );
/*
 * Accept a specific service
 */
cmplz_add_event( 'change', '.cmplz-accept-service', function( e ) {
	const obj = e.target;
	const tagName = obj.tagName;
	const service = obj.getAttribute( 'data-service' );
	if ( typeof service !== 'undefined' ) {
		//inputs are from the consent per service option on the cookie policy.
		if ( tagName === 'INPUT' ) {
			cmplz_set_banner_status( 'dismissed' );
			if ( obj.checked ) {
				cmplz_set_service_consent( service, true );
				cmplz_enable_category( '', service );
			} else {
				cmplz_set_service_consent( service, false );
				//give our track status time to finish
				setTimeout( function() {
					cmplz_reload_browser_compatible();
				}, 500 );
			}
			//if not input, it's a placeholder
		} else {
			e.preventDefault();
			cmplz_set_service_consent( service, true );
			cmplz_enable_category( '', 'general' );
			cmplz_enable_category( '', service );

			//give our track status time to finish
			setTimeout( function() {
				cmplz_reload_browser_compatible();
			}, 500 );
		}
	}
	cmplz_fire_categories_event();
	cmplz_track_status();
} );

/*
 * On the banner, clicking a category should fire the category only after the save button is clicked.
 *
 */
cmplz_add_event( 'click', '.cmplz-save-preferences', function( e ) {
	const obj = e.target;
	cmplz_banner = obj.closest( '.cmplz-cookiebanner' );
	const consented_categories = [];

	for ( const key in cmplz_categories ) {
		if ( cmplz_categories.hasOwnProperty( key ) ) {
			const category = cmplz_categories[ key ];
			const categoryElement = cmplz_banner.querySelector( 'input.cmplz-' + category );
			if ( categoryElement && categoryElement.checked ) {
				consented_categories.push( category );
			}
		}
	}
	cmplz_fire_before_categories_consent( consented_categories );
	for ( const key in cmplz_categories ) {
		if ( cmplz_categories.hasOwnProperty( key ) ) {
			const category = cmplz_categories[ key ];
			if ( consented_categories.includes( category ) ) {
				cmplz_set_consent( category, 'allow' );
			} else {
				cmplz_set_consent( category, 'deny' );
			}
		}
	}
	cmplz_set_banner_status( 'dismissed' );
	cmplz_fire_categories_event();
	cmplz_track_status();
} );

cmplz_add_event( 'click', '.cmplz-close', function() {
	cmplz_set_banner_status( 'dismissed' );
} );

cmplz_add_event( 'click', '.cmplz-view-preferences', function( e ) {
	const obj = e.target;
	cmplz_banner = obj.closest( '.cmplz-cookiebanner' );
	if ( cmplz_banner.querySelector( '.cmplz-categories' ).classList.contains( 'cmplz-fade-in' ) ) {
		cmplz_banner.classList.remove( 'cmplz-categories-visible' );
		cmplz_banner.querySelector( '.cmplz-categories' ).classList.remove( 'cmplz-fade-in' );
		cmplz_banner.querySelector( '.cmplz-view-preferences' ).style.display = 'block';
		cmplz_banner.querySelector( '.cmplz-save-preferences' ).style.display = 'none';
	} else {
		cmplz_banner.classList.add( 'cmplz-categories-visible' );
		cmplz_banner.querySelector( '.cmplz-categories' ).classList.add( 'cmplz-fade-in' );
		cmplz_banner.querySelector( '.cmplz-view-preferences' ).style.display = 'none';
		cmplz_banner.querySelector( '.cmplz-save-preferences' ).style.display = 'block';

		// Set focus to the first category toggle when preferences are shown
		const firstCategory = cmplz_banner.querySelector( '.cmplz-categories .cmplz-category' );
		if ( firstCategory ) {
			const toggleBtn = firstCategory.querySelector( '.cmplz-category-toggle' );
			if ( toggleBtn ) {
				toggleBtn.focus();
			}
		}
	}
} );
/*
 * Toggle category description visibility via the expand/collapse button
 */
cmplz_add_event( 'click', '.cmplz-category-toggle', function( e ) {
	const btn = e.target.closest( '.cmplz-category-toggle' );
	if ( ! btn ) {
		return;
	}
	const isExpanded = btn.getAttribute( 'aria-expanded' ) === 'true';
	btn.setAttribute( 'aria-expanded', String( ! isExpanded ) );
	const category = btn.closest( '.cmplz-category' );
	if ( category ) {
		const desc = category.querySelector( '.cmplz-description' );
		if ( desc ) {
			desc.hidden = isExpanded;
		}
	}
} );
/*
 * On the cookie policy, clicking a category should fire the category immediately
 *
 */
cmplz_add_event( 'change', '.cmplz-manage-consent-container .cmplz-category', function() {
	for ( const key in cmplz_categories ) {
		if ( cmplz_categories.hasOwnProperty( key ) ) {
			const category = cmplz_categories[ key ];
			const categoryElement = document.querySelector( '.cmplz-manage-consent-container input.cmplz-' + category );
			if ( categoryElement ) {
				if ( categoryElement.checked ) {
					cmplz_set_consent( category, 'allow' );
				} else {
					cmplz_set_consent( category, 'deny' );
				}
				cmplz_set_banner_status( 'dismissed' );
				cmplz_fire_categories_event();
				cmplz_track_status();
			}
		}
	}
} );

cmplz_add_event( 'click', '.cmplz-deny', function( e ) {
	e.preventDefault();
	cmplz_set_banner_status( 'dismissed' );
	cmplz_deny_all();
} );

cmplz_add_event( 'click', 'button.cmplz-manage-settings', function( e ) {
	e.preventDefault();
	const catsContainer = document.querySelector( '.cmplz-cookiebanner .cmplz-categories' );
	const saveSettings = document.querySelector( '.cmplz-save-settings' );
	const manageSettings = document.querySelector( 'button.cmplz-manage-settings' );
	if ( ! cmplz_is_hidden( catsContainer ) ) {
		saveSettings.style.display = 'none';
		manageSettings.style.display = 'block';
		catsContainer.style.display = 'none';
	} else {
		saveSettings.style.display = 'block';
		manageSettings.style.display = 'none';
		catsContainer.style.display = 'block';
	}
} );

cmplz_add_event( 'click', 'button.cmplz-manage-consent', function( e ) {
	e.preventDefault();
	cmplz_banner_trigger_source = 'manage-consent';
	cmplz_set_banner_status( 'show' );
} );

/*
 * Handle dismiss on scroll and dismiss on timeout
 */
function cmplz_set_up_auto_dismiss() {
	// If the banner was already dismissed on a previous visit, skip entirely.
	// Without this guard, the timeout/scroll handlers would call cmplz_deny_all()
	// on every page load, revoking consent and triggering an infinite reload loop.
	if ( cmplz_get_banner_status() === 'dismissed' ) {
		return;
	}

	// Handle dismiss on scroll – opt-out only
	if ( complianz.consenttype === 'optout' && Number( complianz.dismiss_on_scroll ) === 1 ) {
		const onWindowScroll = function() {
			if ( window.pageYOffset > Math.floor( 400 ) ) {
				window.removeEventListener( 'scroll', onWindowScroll );
				this.onWindowScroll = null;
				// User may have explicitly accepted/denied before scrolling enough.
				if ( cmplz_get_banner_status() === 'dismissed' ) {
					return;
				}
				cmplz_set_banner_status( 'dismissed' );
				cmplz_fire_categories_event();
				cmplz_track_status();
			}
		};
		window.addEventListener( 'scroll', onWindowScroll );
	}

	// Handle dismiss on timeout
	const delay = parseInt( complianz.dismiss_timeout );
	if ( complianz.consenttype === 'optout' && delay > 0 ) {
		window.setTimeout( function() {
			// User may have explicitly accepted/denied before the timeout fired.
			if ( cmplz_get_banner_status() === 'dismissed' ) {
				return;
			}
			cmplz_set_banner_status( 'dismissed' );
			cmplz_fire_categories_event();
			cmplz_track_status();
		}, Math.floor( delay ) );
	}
}

/*
 * Fire a event wich passes all consented categories
 * Separated from the actual category consent because we want to bundle it in one event
 */
function cmplz_fire_categories_event() {
	const details = new Object();
	details.category = cmplz_highest_accepted_category();
	details.categories = cmplz_accepted_categories();
	details.region = complianz.region;
	const event = new CustomEvent( 'cmplz_fire_categories', { detail: details } );
	document.dispatchEvent( event );
}
/*
 * Track the status of current consent
 * @param status
 */

function cmplz_track_status( status ) {
	let cats = [];
	status = typeof status !== 'undefined' ? status : false;

	const event = new CustomEvent( 'cmplz_track_status', { detail: status } );
	document.dispatchEvent( event );

	if ( ! status ) {
		cats = cmplz_accepted_categories();
	} else {
		cats = [ status ];
	}

	cmplz_set_category_as_body_class();
	let saved_cats, saved_services;
	try {
		saved_cats = JSON.parse( cmplz_get_cookie( 'saved_categories' ) );
	} catch ( e ) {
		saved_cats = {};
	}
	try {
		saved_services = JSON.parse( cmplz_get_cookie( 'saved_services' ) );
	} catch ( e ) {
		saved_services = {};
	}
	const consented_services = cmplz_get_all_service_consents();

	//compare current cats with saved cats. When there are no changes, do nothing.
	if ( cmplz_equals( saved_cats, cats ) && cmplz_equals( saved_services, consented_services ) ) {
		return;
	}

	if ( Number( complianz.store_consent ) !== 1 || cmplz_is_bot() || cmplz_is_speedbot() ) {
		return;
	}

	//keep track of the fact that the status was saved at least once
	cmplz_set_cookie( 'saved_categories', JSON.stringify( cats ) );
	cmplz_set_cookie( 'saved_services', JSON.stringify( consented_services ) );
	cmplz_consent_stored_once = true;

	const data = {
		consented_categories: cats,
		consented_services,
		consenttype: window.wp_consent_type, //store the source consenttype, as our complianz.consenttype will not include optinstats.
	};

	const request = new XMLHttpRequest();
	request.open( 'POST', complianz.url + 'track', true );
	request.setRequestHeader( 'Content-type', 'application/json' );
	request.send( JSON.stringify( data ) );
}

/*
 * Get accepted categories
 *
 * @returns {string}
 */
function cmplz_accepted_categories() {
	let consentedCategories = cmplz_categories;
	const consentedTemp = [];

	//because array filter changes keys, we make a temp arr
	for ( const key in consentedCategories ) {
		if ( consentedCategories.hasOwnProperty( key ) ) {
			const category = consentedCategories[ key ];
			if ( cmplz_has_consent( category ) ) {
				consentedTemp.push( category );
			}
		}
	}

	consentedCategories = consentedCategories.filter( function( value ) {
		return cmplz_in_array( value, consentedTemp );
	} );
	return consentedCategories;
}

/*
 * Enable the checkbox for each category which was enabled
 *
 * */

function cmplz_sync_category_checkboxes() {
	for ( const key in cmplz_categories ) {
		if ( cmplz_categories.hasOwnProperty( key ) ) {
			const category = cmplz_categories[ key ];
			if ( cmplz_has_consent( category ) || category === 'functional' ) {
				document.querySelectorAll( 'input.cmplz-' + category ).forEach( ( obj ) => {
					obj.checked = true;
				} );
			} else {
				document.querySelectorAll( 'input.cmplz-' + category ).forEach( ( obj ) => {
					obj.checked = false;
				} );
			}
		}

		document.querySelectorAll( '.cmplz-accept-service' ).forEach( ( obj ) => {
			const service = obj.getAttribute( 'data-service' );
			const category = obj.getAttribute( 'data-category' );
			if ( cmplz_has_service_consent( service, category ) ) {
				obj.checked = true;
			} else if ( cmplz_is_service_denied( service ) ) {
				obj.checked = false;
			} else {
				//no consent on service level, check if it's category has consent.
				obj.checked = !! cmplz_has_consent( category );
			}
		} );
	}
}

/*
 * Merge two objects
 *
 * */

function cmplz_merge_object( userdata, ajax_data ) {
	const output = {};
	//first, we fill the important data.
	for ( const key in ajax_data ) {
		if ( ajax_data.hasOwnProperty( key ) ) {
			output[ key ] = ajax_data[ key ];
		}
	}

	//conditionally add static data
	for ( const key in userdata ) {
		//only add if not in ajax_data
		if ( ! ajax_data.hasOwnProperty( key ) || typeof ajax_data[ key ] === 'undefined' ) {
			if ( userdata.hasOwnProperty( key ) ) {
				output[ key ] = userdata[ key ];
			}
		}
	}

	return output;
}

/*
 * Clear all our own cookies, to make sure path issues are resolved.
 *
 *
 */
function cmplz_clear_cookies( cookie_part ) {
	if ( typeof document === 'undefined' ) {
		return false;
	}

	let foundCookie = false;
	const secure = window.location.protocol === 'https:' ? ';secure' : '';
	const expires = 'expires=' + new Date().toGMTString();
	const pathParts = location.pathname.replace( /^\/|\/$/g, '' ).split( '/' );

	// Loop through all cookies
	document.cookie.split( '; ' ).forEach( function( cookie ) {
		const cookieName = cookie.split( ';' )[ 0 ].split( '=' )[ 0 ];

		// If the cookie contains cookie_part, try to delete it
		if ( cookieName.indexOf( cookie_part ) !== -1 ) {
			foundCookie = true;
			const domainParts = window.location.hostname.split( '.' );
			const skipLast = domainParts.length > 1;

			// Clear cookies on root path and subpaths
			pathParts.forEach( function( pathPart ) {
				const path = '/' + pathPart;
				document.cookie = encodeURIComponent( cookieName ) + '=;SameSite=Lax' + secure + ';' + expires + ';domain=.' + domainParts.join( '.' ) + ';path=' + path;
				document.cookie = encodeURIComponent( cookieName ) + '=;SameSite=Lax' + secure + ';' + expires + ';domain=.' + domainParts.join( '.' ) + ';path=' + path + '/';
			} );

			// Clear cookies on parent domains
			while ( domainParts.length > 0 ) {
				const domain = '.' + domainParts.join( '.' );
				domainParts.shift();
				if ( skipLast && domainParts.length === 1 ) {
					domainParts.shift();
				}

				pathParts.forEach( function( pathPart ) {
					const path = '/' + pathPart;
					document.cookie = encodeURIComponent( cookieName ) + '=;SameSite=Lax' + secure + ';' + expires + ';domain=' + domain + ';path=' + path;
					document.cookie = encodeURIComponent( cookieName ) + '=;SameSite=Lax' + secure + ';' + expires + ';domain=' + domain + ';path=' + path + '/';
				} );
			}
		}
	} );

	// To prevent a double reload, we preserve the cookie policy id
	cmplz_set_accepted_cookie_policy_id();

	return foundCookie;
}

/*
 *
 * If a policy is accepted, save this in the user policy id
 *
 * */

function cmplz_set_accepted_cookie_policy_id() {
	cmplz_set_cookie( 'policy_id', complianz.current_policy_id );
}

/*
 * For supported integrations, initialize the not consented state
 *
 * */

function cmplz_integrations_init() {
	const cookiesToSet = complianz.set_cookies;
	//check if we have scripts that need to be set to true on init.
	for ( const key in cookiesToSet ) {
		if ( cookiesToSet.hasOwnProperty( key ) && cookiesToSet[ key ][ 1 ] === '1' ) {
			cmplz_set_cookie( key, cookiesToSet[ key ][ 1 ], false );
		}
	}
}

/*
 * For supported integrations, revoke consent
 *
 * */
function cmplz_integrations_revoke() {
	const cookiesToSet = complianz.set_cookies;
	for ( const key in cookiesToSet ) {
		if ( cookiesToSet.hasOwnProperty( key ) ) {
			const revokeValue = cookiesToSet[ key ][ 1 ];
			cmplz_set_cookie( key, revokeValue, false );
			// A zero/empty revoke value (e.g. integer 0 from some integrations)
			// means "clear this cookie" — preserves the original == false behaviour.
			// A meaningful value such as '1' (e.g. dont_track_me) is kept, not cleared.
			if ( Number( revokeValue ) === 0 ) {
				cmplz_clear_cookies( key );
			}
		}
	}
}

/*
 * For supported integrations, set consent
 *
 * */

function cmplz_set_integrations_cookies() {
	const cookiesToSet = complianz.set_cookies;
	for ( const key in cookiesToSet ) {
		if ( cookiesToSet.hasOwnProperty( key ) ) {
			cmplz_set_cookie( key, cookiesToSet[ key ][ 0 ], false );
		}
	}
}

function cmplz_get_url_parameter( sPageURL, sParam ) {
	if ( ! sPageURL || typeof sPageURL === 'undefined' || sPageURL.indexOf( '?' ) === -1 ) {
		return false;
	}

	const queryString = sPageURL.split( '?' )[ 1 ];
	if ( ! queryString ) {
		return false;
	}

	const sURLVariables = queryString.split( '&' );
	for ( let i = 0; i < sURLVariables.length; i++ ) {
		const sParameterName = sURLVariables[ i ].split( '=' );
		if ( sParameterName[ 0 ] === sParam ) {
			return sParameterName[ 1 ] === undefined ? true : decodeURIComponent( sParameterName[ 1 ] );
		}
	}

	return false;
}

/*
 * If the parameter cmplz_region_redirect =true is passed, find the user's region, and redirect.
 */
function cmplz_maybe_auto_redirect() {
	const redirect = cmplz_get_url_parameter( window.location.href, 'cmplz_region_redirect' );
	const region = cmplz_get_url_parameter( window.location.href, 'cmplz-region' );
	if ( redirect && ! region ) {
		const newUrl = window.location.href.split( '#' )[ 0 ] + '&cmplz-region=' + complianz.region;
		const hash = window.location.hash;
		window.location.href = newUrl + hash;
	}
}

/*
 * wrapper to set consent for wp consent API. If consent API is not active, do nothing
 * @param type
 * @param value
 */
function cmplz_wp_set_consent( type, value ) {
	//wp consent api integration
	if ( typeof wp_set_consent === 'function' ) {
		wp_set_consent( type, value );
	}
}

function cmplz_start_clean() {
	if ( Number( complianz.clean_cookies ) === 1 ) {
		//check if it's already stored
		if ( typeof ( Storage ) !== 'undefined' ) {
			cmplz_cookie_data = JSON.parse( sessionStorage.getItem( 'cmplz_cookie_data' ) );
		}
		//if not stored yet, load. As features in the user object can be changed on updates, we also check for the version
		if ( ! cmplz_cookie_data || cmplz_cookie_data.length === 0 ) {
			const request = new XMLHttpRequest();
			request.open( 'GET', complianz.url + 'cookie_data', true );
			request.setRequestHeader( 'Content-type', 'application/json' );
			request.send();
			request.onload = function() {
				cmplz_cookie_data = JSON.parse( request.response );
				sessionStorage.setItem( 'cmplz_cookie_data', JSON.stringify( cmplz_cookie_data ) );
				cmplz_setup_clean_interval();
			};
		} else {
			cmplz_setup_clean_interval();
		}
	}
}

/*
* Execute the cleanup of cookies
*/
function cmplz_do_cleanup() {
	const consent_categories = [ 'preferences', 'statistics', 'marketing' ];
	for ( const category of consent_categories ) {
		if ( ! cmplz_has_consent( category ) && cmplz_cookie_data.hasOwnProperty( category ) ) {
			const services = cmplz_cookie_data[ category ];

			for ( const service in services ) {
				if ( ! cmplz_has_service_consent( service, category ) ) {
					const cookies = services[ service ];

					for ( const item of cookies ) {
						cmplz_clear_cookies( item );
						cmplz_clear_storage( item );
					}
				}
			}
		}
	}
}

function cmplz_setup_clean_interval() {
	// if the cookie data array is empty, return, nothing to do.
	if ( ! cmplz_cookie_data ) {
		return;
	}
	//check if it's already activated
	if ( cmplzCleanCookieInterval ) {
		return;
	}
	//one straight away, then every second
	cmplz_do_cleanup();
	cmplzCleanCookieInterval = setInterval( cmplz_do_cleanup, 1000 );
}

/*
 * Clear an item from either session or localstorage
 * @param item
 */
function cmplz_clear_storage( item ) {
	if ( typeof ( Storage ) !== 'undefined' ) {
		if ( localStorage.getItem( item ) ) {
			localStorage.removeItem( item );
		}
		if ( sessionStorage.getItem( item ) ) {
			sessionStorage.removeItem( item );
		}
	}
}
/*
 * Load revoke options
 */

function cmplz_load_manage_consent_container() {
	//don't load manage html code in the block editor
	const manage_consent_container = document.querySelector( '.cmplz-manage-consent-container' );
	const is_block_editor = false;//document.querySelector('.wp-admin .cmplz-unlinked-mode');
	if ( manage_consent_container && ! is_block_editor ) {
		const request = new XMLHttpRequest();
		request.open( 'GET', complianz.url + 'manage_consent_html?' + complianz.locale, true );
		request.setRequestHeader( 'Content-type', 'application/json' );
		request.send();
		request.onload = function() {
			const html = JSON.parse( request.response );
			manage_consent_container.insertAdjacentHTML( 'beforeend', html );
			cmplz_sync_category_checkboxes();
			const nojavascript = document.querySelector( '#cmplz-manage-consent-container-nojavascript' );
			nojavascript.style.display = 'none';
			manage_consent_container.style.display = 'block';
			const event = new CustomEvent( 'cmplz_manage_consent_container_loaded' );
			document.dispatchEvent( event );
		};
	}
}

/*
 * Make slider radio's tabable
 */

cmplz_add_event( 'keypress', '.cmplz-banner-slider label', function( e ) {
	const keycode = ( e.keyCode ? e.keyCode : e.which );
	if ( keycode === 32 ) {
		e.target.click();
	}
} );

/*
 * Make close button closable with enter
 */
cmplz_add_event( 'keypress', '.cmplz-cookiebanner .cmplz-header .cmplz-close', function( e ) {
	const keycode = ( e.keyCode ? e.keyCode : e.which );
	if ( keycode === 13 ) {
		e.target.click();
	}
} );

/*
 * Compare two arrays
 * @param array
 * @returns {boolean}
 */
function cmplz_equals( array_1, array_2 ) {
	// If both are objects (not arrays), compare their values properly
	if ( typeof array_1 === 'object' && array_1 !== null && ! Array.isArray( array_1 ) &&
		typeof array_2 === 'object' && array_2 !== null && ! Array.isArray( array_2 ) ) {
		// Get all unique keys from both objects
		const allKeys = [ ...new Set( [ ...Object.keys( array_1 ), ...Object.keys( array_2 ) ] ) ];

		// Compare each key's value
		for ( const key of allKeys ) {
			if ( array_1[ key ] !== array_2[ key ] ) {
				return false;
			}
		}
		return true;
	}

	// Original array comparison logic
	if ( ! Array.isArray( array_1 ) ) {
		array_1 = Object.keys( array_1 );
		array_2 = Object.keys( array_2 );
	}
	// if the other array is a falsy value, return
	if ( ! array_1 || ! array_2 ) {
		return false;
	}

	// compare lengths - can save a lot of time
	if ( array_1.length !== array_2.length ) {
		return false;
	}

	for ( let i = 0, l = array_1.length; i < l; i++ ) {
		// Check if we have nested arrays
		if ( array_1[ i ] instanceof Array && array_2[ i ] instanceof Array ) {
			// recurse into the nested arrays
			if ( ! cmplz_equals( array_1[ i ], array_2[ i ] ) ) {
				return false;
			}
		} else if ( array_1[ i ] !== array_2[ i ] ) {
			return false;
		}
	}
	return true;
}

/*
* Copy all element atributes to the new element
*/
function cmplzCopyAttributes( source, target ) {
	const excludes = [ 'type', 'data-service', 'data-category', 'async' ];
	Array.from( source.attributes ).forEach( ( attribute ) => {
		if ( attribute.nodeName === 'data-script-type' && attribute.nodeValue === 'module' ) {
			target.setAttribute( 'type', 'module' );
			target.removeAttribute( 'data-script-type' );
		} else if ( ! excludes.includes( attribute.nodeName ) ) {
			target.setAttribute( attribute.nodeName, attribute.nodeValue );
		}
	} );
}

/*
 * Hooked into jquery
 */
const cmplz_has_wp_video = document.querySelector( '.cmplz-wp-video-shortcode' );
let cmplz_times_checked = 0;
if ( 'undefined' !== typeof window.jQuery ) {
	jQuery( document ).ready( function( $ ) {
		if ( cmplz_has_wp_video ) {
			document.addEventListener( 'cmplz_enable_category', function() {
				cmplz_activate_wp_video();
			} );

			const cmplzInterval = setInterval( function() {
				cmplz_times_checked += 1;
				if ( document.querySelector( '.cmplz-wp-video-shortcode' ) && cmplz_times_checked < 100 ) {
					cmplz_activate_wp_video();
				} else {
					clearInterval( cmplzInterval );
				}
			}, 500 );
		}

		/*
		 * WordPress legacy shortcode
		 */
		function cmplz_activate_wp_video() {
			if ( ! document.querySelector( '.cmplz-wp-video-shortcode' ) ) {
				return;
			}
			const categories = cmplz_accepted_categories();
			const services = cmplz_get_all_service_consents();
			let selectorVideo = '';
			const selectorVideos = [];
			for ( const c_key in categories ) {
				if ( categories.hasOwnProperty( c_key ) ) {
					const category = categories[ c_key ];
					if ( category === 'functional' ) {
						break;
					}
					selectorVideos.push( '.cmplz-wp-video-shortcode[data-category="' + category + '"]' );
				}
			}
			for ( const s_key in services ) {
				if ( services.hasOwnProperty( s_key ) ) {
					selectorVideos.push( '.cmplz-wp-video-shortcode[data-service="' + s_key + '"]' );
				}
			}
			selectorVideo = selectorVideos.join( ',' );
			let should_initialize_video = false;
			if ( selectorVideo.length > 0 ) {
				document.querySelectorAll( selectorVideo ).forEach( ( obj ) => {
					should_initialize_video = true;
					obj.setAttribute( 'controls', 'controls' );
					obj.classList.add( 'wp-video-shortcode', 'cmplz-processed' );
					obj.classList.remove( 'cmplz-wp-video-shortcode' );
					obj.closest( '.cmplz-wp-video' ).classList.remove( 'cmplz-wp-video' );

					const blocked_notice = obj.closest( '.wp-video' ).querySelector( '.cmplz-blocked-content-notice' );
					if ( blocked_notice ) {
						blocked_notice.parentElement.removeChild( blocked_notice );
					}
					obj.classList.remove( 'cmplz-blocked-content-container' );
				} );
			}

			if ( should_initialize_video ) {
				//in normal setup, this should work. e.g. with primavera theme not, then we init per element.
				if ( window.wp.mediaelement ) {
					window.wp.mediaelement.initialize();
				} else {
					const settings = {};
					settings.videoWidth = '100%';
					settings.videoHeight = '100%';
					settings.enableAutosize = true;
					$( '.wp-video-shortcode' ).mediaelementplayer( settings );
				}
			}
		}

		/*
		 * Activate fitvids on the parent element if active
		 *  a.o. Beaverbuilder
		 */
		document.querySelectorAll( '.cmplz-video.cmplz-activated' ).forEach( ( obj ) => {
			cmplz_activate_fitvids( obj );
		} );

		document.addEventListener( 'cmplz_enable_category', function() {
			document.querySelectorAll( '.cmplz-video.cmplz-activated' ).forEach( ( obj ) => {
				cmplz_activate_fitvids( obj );
			} );
		} );

		function cmplz_activate_fitvids( obj ) {
			//turn obj into jquery object
			const $obj = $( obj );
			if ( typeof $obj.parent().fitVids === 'function' ) {
				$obj.parent().fitVids();
			}
		}
	} );
}
