( function () {
	'use strict';

	var firstTouchCookie = 'carticy_conversions_first_touch';
	var lastTouchCookie = 'carticy_conversions_last_touch';
	var gaClientCookie = 'carticy_conversions_ga_client_id';
	var gaSessionCookie = 'carticy_conversions_ga_session_id';
	var consentSignalsCookie = 'carticy_conversions_consent_signals';
	var consentModeCookie = 'carticy_conversions_consent_mode';
	var touchKeys = [ 'utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term', 'gclid', 'fbclid', 'wbraid', 'gbraid', 'gad_source', 'dclid' ];
	var explicitStates = {
		granted: [ '1', 'accept', 'accepted', 'allow', 'allowed', 'approved', 'consent', 'consented', 'grant', 'granted', 'on', 'true', 'yes' ],
		denied: [ '0', 'decline', 'declined', 'deny', 'denied', 'disallow', 'disallowed', 'false', 'no', 'off', 'optout', 'rejected', 'reject' ]
	};

	function encode( value ) {
		return encodeURIComponent( JSON.stringify( value ) );
	}

	function cookieSuffix( days ) {
		var expires = new Date( Date.now() + days * 86400000 ).toUTCString();
		var secure = 'https:' === window.location.protocol ? '; Secure' : '';
		return '; expires=' + expires + '; path=/; SameSite=Lax' + secure;
	}

	function setCookie( name, value, days ) {
		document.cookie = name + '=' + value + cookieSuffix( days );
	}

	function readCookie( name ) {
		var match = document.cookie.match( new RegExp( '(?:^|; )' + name.replace( /([.$?*|{}()[\]\\/+^])/g, '\\$1' ) + '=([^;]*)' ) );
		if ( ! match ) {
			return '';
		}
		try {
			return decodeURIComponent( match[1] );
		} catch ( error ) {
			return match[1];
		}
	}

	function readJsonCookie( name ) {
		var value = readCookie( name );
		if ( ! value ) {
			return null;
		}
		try {
			return JSON.parse( value );
		} catch ( error ) {
			return null;
		}
	}

	function currentTouch() {
		var params = new URLSearchParams( window.location.search );
		var touch = {};
		touchKeys.forEach( function ( key ) {
			var value = params.get( key );
			if ( value ) {
				touch[ key ] = value;
			}
		} );
		if ( ! Object.keys( touch ).length ) {
			return null;
		}
		touch.landing_page = window.location.href;
		touch.referrer = document.referrer || '';
		touch.timestamp = new Date().toISOString();
		return touch;
	}

	function captureAttribution() {
		var touch = currentTouch();
		if ( ! touch ) {
			return;
		}
		if ( ! readCookie( firstTouchCookie ) ) {
			setCookie( firstTouchCookie, encode( touch ), 30 );
		}
		setCookie( lastTouchCookie, encode( touch ), 7 );
	}

	function parseGaClientId( value ) {
		var match = String( value || '' ).match( /^GA\d+\.\d+\.(\d+\.\d+)$/ );
		return match ? match[1] : ( /^\d+\.\d+$/.test( value || '' ) ? value : '' );
	}

	function parseGaSessionId( value ) {
		var text = String( value || '' );
		var gs1 = text.match( /^GS\d+\.\d+\.(\d+)\./ );
		var gs2 = text.match( /(?:^|[.$])s(\d+)(?:[.$]|$)/ );
		if ( /^\d+$/.test( text ) ) {
			return text;
		}
		return gs1 ? gs1[1] : ( gs2 ? gs2[1] : '' );
	}

	function decodeCookieValue( value ) {
		try {
			return decodeURIComponent( value );
		} catch ( error ) {
			return value;
		}
	}

	function captureGaCookies() {
		var found = {
			client: false,
			session: false
		};
		var cookies = document.cookie.split( /;\s*/ );
		cookies.forEach( function ( row ) {
			var parts = row.split( '=' );
			var name = parts.shift();
			var value = decodeCookieValue( parts.join( '=' ) || '' );
			if ( '_ga' === name ) {
				var clientId = parseGaClientId( value );
				if ( clientId ) {
					setCookie( gaClientCookie, clientId, 7 );
					found.client = true;
				}
			}
			if ( /^_ga_[A-Z0-9]+$/.test( name ) ) {
				var sessionId = parseGaSessionId( value );
				if ( sessionId ) {
					setCookie( gaSessionCookie, sessionId, 1 );
					found.session = true;
				}
			}
		} );
		return found;
	}

	function pollGaCookies() {
		var attempts = 0;
		var initial = captureGaCookies();
		if ( initial.client && initial.session ) {
			injectHiddenFields();
			return;
		}
		var timer = window.setInterval( function () {
			var found = captureGaCookies();
			attempts += 1;
			injectHiddenFields();
			if ( attempts >= 10 || ( found.client && found.session ) ) {
				window.clearInterval( timer );
			}
		}, 500 );
	}

	function normalizeConsentState( value ) {
		var normalized;
		if ( true === value ) {
			return 'granted';
		}
		if ( false === value ) {
			return 'denied';
		}
		if ( 'number' === typeof value ) {
			return 1 === value ? 'granted' : ( 0 === value ? 'denied' : 'unset' );
		}
		normalized = String( value || '' ).toLowerCase().replace( /[\s_-]+/g, '' );
		if ( -1 !== explicitStates.granted.indexOf( normalized ) ) {
			return 'granted';
		}
		if ( -1 !== explicitStates.denied.indexOf( normalized ) ) {
			return 'denied';
		}
		return 'unset';
	}

	function pushSignal( signal ) {
		if ( ! signal || ! signal.source ) {
			return;
		}
		var signals = readJsonCookie( consentSignalsCookie ) || [];
		// Replace this source's previous signal for the same category scope
		// regardless of state: keeping an older "denied" alongside a newer
		// "granted" would let the deny-dominant resolver latch the shopper to
		// denied for the cookie's whole lifetime. The latest reading per
		// source+scope IS that source's decision; other sources (and other
		// category scopes of the same source, e.g. WP Consent API's
		// per-category signals) keep their own entries.
		var scopeKey = function ( entry ) {
			return ( entry.source || '' ) + '|' + ( entry.source_version || '' ) + '|'
				+ Object.keys( entry.categories || {} ).sort().join( ',' );
		};
		var signalScope = scopeKey( signal );
		signals = signals.filter( function ( existing ) {
			return scopeKey( existing ) !== signalScope;
		} );
		signals.push( signal );
		setCookie( consentSignalsCookie, encode( signals ), 7 );

		// Same-page consumers (the tracking dispatcher's consent replay)
		// subscribe to this event instead of wrapping the bus function, so
		// the integration survives any script load order.
		try {
			document.dispatchEvent( new CustomEvent( 'carticy_conversions_consent_signal', { detail: signal } ) );
		} catch ( error ) {}
	}

	function consentStateFromDataLayerEntry( entry ) {
		if ( ! entry ) {
			return null;
		}
		if ( Array.isArray( entry ) && 'consent' === entry[0] && 'update' === entry[1] && entry[2] ) {
			return entry[2];
		}
		if ( 'object' === typeof entry && 'consent' === entry[0] && 'update' === entry[1] && entry[2] ) {
			return entry[2];
		}
		return null;
	}

	function captureConsentModeFromDataLayer() {
		window.dataLayer = window.dataLayer || [];
		var consent = null;
		window.dataLayer.forEach( function ( entry ) {
			var state = consentStateFromDataLayerEntry( entry );
			if ( state ) {
				consent = state;
			}
		} );
		if ( consent ) {
			setCookie( consentModeCookie, encode( consent ), 1 );
		}
	}

	function captureConsentMode() {
		captureConsentModeFromDataLayer();
		if ( 'function' !== typeof window.gtag ) {
			return;
		}
		window.dataLayer.push( function () {
			var consent = this && this.get && this.get( 'consent' );
			if ( consent ) {
				setCookie( consentModeCookie, encode( consent ), 1 );
			}
		} );
	}

	function wpConsentSignal( category, state ) {
		var services = {};
		var categories = {};
		var normalizedState = normalizeConsentState( state );
		if ( 'unset' === normalizedState ) {
			return;
		}
		categories[ category ] = normalizedState;
		if ( 'marketing' === category ) {
			services.meta = categories[ category ];
			services[ 'google-ads' ] = categories[ category ];
		}
		if ( 'statistics' === category ) {
			services.ga4 = categories[ category ];
		}
		// Mirrors the PHP intake default: wp_set_consent() fires for CMP
		// page-load syncs too (including opt-out-regime defaults where
		// marketing is "allow" without any user action), so a consent-change
		// callback is NOT proof of an explicit decision. Merchants whose CMP
		// only emits real decisions opt in via the same flag the PHP filter
		// controls.
		pushSignal( {
			source: 'wp-consent-api-js',
			state: categories[ category ],
			is_explicit: true === ( window.carticyConversionsConsentConfig || {} ).wpConsentApiExplicit,
			categories: categories,
			services: services,
			source_version: 'wp-consent-api',
			captured_at: new Date().toISOString()
		} );
	}

	function listenForWpConsentApi() {
		if ( 'function' !== typeof window.wp_listen_for_consent_change ) {
			return;
		}
		[ 'marketing', 'statistics', 'preferences', 'functional' ].forEach( function ( category ) {
			try {
				window.wp_listen_for_consent_change( category, function ( state ) {
					wpConsentSignal( category, state );
				} );
			} catch ( error ) {}
		} );
	}

	function readGlobalTarget( path ) {
		var owner = window;
		var value = window;
		var parts = String( path || '' ).split( '.' );
		var index;
		for ( index = 0; index < parts.length; index += 1 ) {
			if ( ! parts[ index ] ) {
				return null;
			}
			owner = value;
			value = value && value[ parts[ index ] ];
			if ( 'undefined' === typeof value || null === value ) {
				return null;
			}
		}
		return {
			owner: owner,
			value: value
		};
	}

	function readAdapterValue( adapter ) {
		var target = readGlobalTarget( adapter.globalPath );
		if ( ! target ) {
			return undefined;
		}
		if ( true === adapter.call && 'function' === typeof target.value ) {
			try {
				return target.value.call( target.owner );
			} catch ( error ) {
				return undefined;
			}
		}
		return target.value;
	}

	function stateFromPattern( value, grantPattern, denyPattern ) {
		var text;
		try {
			text = 'string' === typeof value ? value : JSON.stringify( value || {} );
		} catch ( error ) {
			text = String( value || '' );
		}
		if ( 'undefined' === typeof text ) {
			text = String( value || '' );
		}
		try {
			if ( denyPattern && new RegExp( denyPattern, 'i' ).test( text ) ) {
				return 'denied';
			}
			if ( grantPattern && new RegExp( grantPattern, 'i' ).test( text ) ) {
				return 'granted';
			}
		} catch ( error ) {
			return 'unset';
		}
		return 'unset';
	}

	function captureGenericGlobalAdapters() {
		var adapters = window.carticyConversionsConsentAdapters || [];
		if ( ! Array.isArray( adapters ) ) {
			return;
		}
		adapters.forEach( function ( adapter ) {
			var value = readAdapterValue( adapter );
			var categories = {};
			var services = {};
			var isExplicit = true;
			var valueText;
			if ( 'undefined' === typeof value || ! adapter.source || ! adapter.categories ) {
				return;
			}
			Object.keys( adapter.categories ).forEach( function ( category ) {
				var rule = adapter.categories[ category ] || {};
				var state = stateFromPattern( value, rule.grantPattern, rule.denyPattern );
				if ( 'unset' !== state ) {
					categories[ category ] = state;
				}
			} );
			// Mirrors the PHP preset semantics: only adapters whose source
			// enumerates every category (declared in the adapter data) may
			// treat a pattern miss as denial; the generic default is unset
			// (omit the service) so a format drift never fabricates an
			// explicit denial.
			Object.keys( adapter.services || {} ).forEach( function ( service ) {
				var category = adapter.services[ service ];
				var state = categories[ category ] || adapter.missingCategoryState || 'unset';
				if ( 'unset' !== state ) {
					services[ service ] = state;
				}
			} );
			// Cookiebot-style implied consent is not an affirmative act.
			if ( adapter.explicitPattern ) {
				try {
					valueText = 'string' === typeof value ? value : JSON.stringify( value || {} );
					isExplicit = new RegExp( adapter.explicitPattern, 'i' ).test( valueText );
				} catch ( error ) {
					isExplicit = false;
				}
			}
			if ( Object.keys( categories ).length ) {
				pushSignal( {
					source: adapter.source,
					state: Object.keys( categories ).some( function ( key ) { return 'denied' === categories[ key ]; } ) ? 'denied' : 'granted',
					is_explicit: isExplicit,
					categories: categories,
					services: services,
					source_version: adapter.sourceVersion || 'generic-js-global',
					captured_at: new Date().toISOString()
				} );
			}
		} );
	}

	function runConsentCapture() {
		captureConsentMode();
		captureGenericGlobalAdapters();
	}

	function entryHasDataLayerEvent( entry, events ) {
		if ( ! entry || ! events.length ) {
			return false;
		}
		if ( 'object' === typeof entry && -1 !== events.indexOf( entry.event ) ) {
			return true;
		}
		if ( Array.isArray( entry ) ) {
			return entry.some( function ( value ) {
				return value && 'object' === typeof value && -1 !== events.indexOf( value.event );
			} );
		}
		return false;
	}

	function listenForGenericAdapterEvents() {
		var adapters = window.carticyConversionsConsentAdapters || [];
		var dataLayerEvents = [];
		if ( ! Array.isArray( adapters ) ) {
			return;
		}
		adapters.forEach( function ( adapter ) {
			( adapter.events || [] ).forEach( function ( eventName ) {
				if ( eventName && 'function' === typeof window.addEventListener ) {
					window.addEventListener( eventName, runConsentCapture );
				}
			} );
			( adapter.subscriptions || [] ).forEach( function ( subscription ) {
				var target;
				if ( ! subscription || ! subscription.path || ! subscription.event ) {
					return;
				}
				target = readGlobalTarget( subscription.path );
				if ( ! target || 'function' !== typeof target.value ) {
					return;
				}
				try {
					target.value.call( target.owner, subscription.event, runConsentCapture );
				} catch ( error ) {}
			} );
			dataLayerEvents = dataLayerEvents.concat( adapter.dataLayerEvents || [] );
		} );

		window.dataLayer = window.dataLayer || [];
		dataLayerEvents = dataLayerEvents.filter( function ( eventName, index ) {
			return eventName && dataLayerEvents.indexOf( eventName ) === index;
		} );
		if ( dataLayerEvents.length && ! window.dataLayer.carticyConversionsWrapped ) {
			var originalPush = window.dataLayer.push;
			window.dataLayer.push = function () {
				var result = originalPush.apply( window.dataLayer, arguments );
				Array.prototype.slice.call( arguments ).forEach( function ( entry ) {
					if ( consentStateFromDataLayerEntry( entry ) || entryHasDataLayerEvent( entry, dataLayerEvents ) ) {
						runConsentCapture();
					}
				} );
				return result;
			};
			window.dataLayer.carticyConversionsWrapped = true;
		}
		window.dataLayer.forEach( function ( entry ) {
			if ( consentStateFromDataLayerEntry( entry ) || entryHasDataLayerEvent( entry, dataLayerEvents ) ) {
				runConsentCapture();
			}
		} );
	}

	function injectHiddenFields() {
		var values = {};
		values[ firstTouchCookie ] = readCookie( firstTouchCookie );
		values[ lastTouchCookie ] = readCookie( lastTouchCookie );
		values[ gaClientCookie ] = readCookie( gaClientCookie );
		values[ gaSessionCookie ] = readCookie( gaSessionCookie );
		values.carticy_conversions_fbp = readCookie( '_fbp' );
		values.carticy_conversions_fbc = readCookie( '_fbc' );

		document.querySelectorAll( 'form.checkout, form.woocommerce-checkout' ).forEach( function ( form ) {
			Object.keys( values ).forEach( function ( name ) {
				if ( ! values[ name ] ) {
					return;
				}
				var input = form.querySelector( 'input[name="' + name + '"]' );
				if ( ! input ) {
					input = document.createElement( 'input' );
					input.type = 'hidden';
					input.name = name;
					form.appendChild( input );
				}
				input.value = values[ name ];
			} );
		} );
	}

	window.carticyConversionsConsent = window.carticyConversionsConsent || {};
	window.carticyConversionsConsent.pushSignal = pushSignal;

	captureAttribution();
	pollGaCookies();
	listenForWpConsentApi();
	listenForGenericAdapterEvents();
	runConsentCapture();
	injectHiddenFields();
	document.addEventListener( 'submit', function () {
		captureGaCookies();
		runConsentCapture();
		injectHiddenFields();
	}, true );
}() );
