import './frontend.css';
import { createDeliveryController } from './delivery';
import {
	isHiddenForViewport,
	MOBILE_MAX_WIDTH_PX,
	notificationClassNames,
	styleCssVars,
} from './notification-presentation';

( function () {
	'use strict';
	const config = window.voidPulse;
	if ( ! config || ! config.rest ) {
		return;
	}

	const timers = [];
	const delivery = createDeliveryController( config.delivery );
	let index = 0;
	let shown = 0;
	let events = [];
	let eventToken = '';
	let stopped = false;
	let currentNode: HTMLElement | null = null;

	function isMobileViewport() {
		return window.matchMedia( '(max-width: ' + MOBILE_MAX_WIDTH_PX + 'px)' )
			.matches;
	}
	const bytes = new Uint8Array( 18 );
	const cryptoApi = window.crypto || window.msCrypto;
	let client = '';
	let conversionSession = '';
	function readLocal( key ) {
		try {
			return window.localStorage.getItem( key ) || '';
		} catch ( error ) {
			return '';
		}
	}
	function writeLocal( key, value ) {
		try {
			window.localStorage.setItem( key, value );
		} catch ( error ) {}
	}
	if ( cryptoApi && cryptoApi.getRandomValues ) {
		cryptoApi.getRandomValues( bytes );
		client = Array.from( bytes, function ( value ) {
			return value.toString( 36 );
		} ).join( '' );
	}
	if ( config.conversionTracking ) {
		conversionSession = readLocal( 'voidpulse-conversion-session' );
		if ( ! conversionSession && cryptoApi?.getRandomValues ) {
			const conversionBytes = new Uint8Array( 24 );
			cryptoApi.getRandomValues( conversionBytes );
			conversionSession = Array.from(
				conversionBytes,
				function ( value ) {
					return value.toString( 36 );
				}
			).join( '' );
			writeLocal( 'voidpulse-conversion-session', conversionSession );
		}
	}

	function trackConversion( metric ) {
		const tracking = config.conversionTracking;
		if ( ! tracking || ! conversionSession ) {
			return;
		}
		const types = {
			impressions: 'impression',
			clicks: 'click',
			dismissals: 'dismissal',
		};
		const type = types[ metric ];
		if ( ! type ) {
			return;
		}
		if ( 'click' === type ) {
			writeLocal(
				'voidpulse-attribution-context',
				JSON.stringify( {
					campaignId: tracking.campaign_id,
					recordedAt: Date.now(),
				} )
			);
		}
		let device = 'desktop';
		if ( window.innerWidth < 600 ) {
			device = 'mobile';
		} else if ( window.innerWidth < 1024 ) {
			device = 'tablet';
		}
		fetch( tracking.rest + 'interaction', {
			method: 'POST',
			credentials: 'same-origin',
			keepalive: true,
			headers: { 'Content-Type': 'application/json' },
			body: JSON.stringify( {
				campaign_id: tracking.campaign_id,
				session: conversionSession,
				type,
				product_id: 0,
				token: tracking.token,
				page: window.location.href,
				device,
			} ),
		} ).catch( function () {} );
	}

	function track( metric ) {
		trackConversion( metric );
		if ( ! client || ! eventToken ) {
			return;
		}
		fetch( config.rest + 'interaction', {
			method: 'POST',
			credentials: 'same-origin',
			headers: { 'Content-Type': 'application/json' },
			body: JSON.stringify( {
				metric,
				client,
				token: eventToken,
			} ),
		} ).catch( function () {} );
	}

	function schedule( callback, seconds ) {
		const timer = window.setTimeout( callback, seconds * 1000 );
		timers.push( timer );
		return timer;
	}

	function pauseAutoDismiss( node, seconds, callback ) {
		let remaining = seconds * 1000;
		let started = 0;
		let timer = 0;
		const pauses = new Set();
		function arm() {
			started = Date.now();
			timer = window.setTimeout( callback, Math.max( 0, remaining ) );
			timers.push( timer );
		}
		function pause( reason ) {
			if ( ! pauses.size && timer ) {
				remaining -= Date.now() - started;
				window.clearTimeout( timer );
				timer = 0;
			}
			pauses.add( reason );
		}
		function resume( reason ) {
			pauses.delete( reason );
			if (
				! pauses.size &&
				! timer &&
				! node.classList.contains( 'voidpulse--leaving' )
			) {
				arm();
			}
		}
		node.addEventListener( 'pointerenter', function () {
			pause( 'pointer' );
		} );
		node.addEventListener( 'pointerleave', function () {
			resume( 'pointer' );
		} );
		node.addEventListener( 'focusin', function () {
			pause( 'focus' );
		} );
		node.addEventListener( 'focusout', function ( event ) {
			if ( ! node.contains( event.relatedTarget ) ) {
				resume( 'focus' );
			}
		} );
		arm();
	}

	function clearNotification( node ) {
		if (
			! node ||
			! node.parentNode ||
			node.classList.contains( 'voidpulse--leaving' )
		) {
			return;
		}
		if ( currentNode === node ) {
			currentNode = null;
		}
		node.classList.add( 'voidpulse--leaving' );
		delivery.setNextDelay( config.timing.gap );
		schedule(
			function () {
				if ( node.parentNode ) {
					node.parentNode.removeChild( node );
				}
				if (
					! stopped &&
					shown < config.timing.maximum &&
					( config.timing.loop || index < events.length )
				) {
					schedule( showNext, config.timing.gap );
				}
			},
			Math.max( 0.15, config.timing.animation_duration / 1000 )
		);
	}

	function showNext() {
		if ( document.hidden && config.timing.pause_hidden ) {
			schedule( showNext, 1 );
			return;
		}
		if ( ! events.length || shown >= config.timing.maximum ) {
			return;
		}
		if ( isHiddenForViewport( config.display || {}, isMobileViewport() ) ) {
			return;
		}
		let event = null;
		let attempts = 0;
		let retryDelay = null;
		while ( attempts < events.length ) {
			const candidate = events[ index % events.length ];
			index += 1;
			attempts += 1;
			const delay = delivery.nextDisplayDelay( candidate.event_key );
			if ( 0 === delay ) {
				event = candidate;
				break;
			}
			if ( null !== delay ) {
				retryDelay =
					null === retryDelay ? delay : Math.min( retryDelay, delay );
			}
		}
		if ( ! event ) {
			if ( null !== retryDelay ) {
				schedule( showNext, retryDelay );
			}
			return;
		}
		shown += 1;

		const node = document.createElement( 'aside' );
		node.className = notificationClassNames( {
			prefix: 'voidpulse',
			template: config.template,
			position: config.style.desktop_position,
			animation: config.timing.animation,
		} );
		node.setAttribute( 'aria-label', config.labels.activity );
		node.setAttribute( 'aria-live', 'off' );
		const vars = styleCssVars(
			'voidpulse',
			config.style as Record< string, unknown >
		);
		Object.entries( vars ).forEach( ( [ key, value ] ) => {
			if ( value !== '' ) {
				node.style.setProperty( key, value );
			}
		} );
		node.style.setProperty(
			'--voidpulse-animation-duration',
			config.timing.animation_duration + 'ms'
		);
		node.style.setProperty(
			'--voidpulse-animation-easing',
			config.timing.animation_easing === 'spring'
				? 'cubic-bezier(0.22, 1.35, 0.36, 1)'
				: config.timing.animation_easing
		);
		node.dataset.mobilePosition = config.style.mobile_position;

		if (
			config.style.show_image &&
			event.product_image &&
			delivery.allowImages()
		) {
			const image = document.createElement( 'img' );
			image.className =
				'voidpulse__image voidpulse__image--' +
				config.style.image_shape;
			image.src = event.product_image;
			image.alt = '';
			image.loading = 'lazy';
			image.width = 64;
			image.height = 64;
			node.appendChild( image );
		}

		const copy = document.createElement( 'div' );
		copy.className = 'voidpulse__copy';
		if ( config.style.show_label ) {
			const label = document.createElement( 'span' );
			label.className = 'voidpulse__label';
			label.textContent = config.labels.activity;
			copy.appendChild( label );
		}
		if ( config.style.show_headline ) {
			const headline = document.createElement( 'strong' );
			headline.className = 'voidpulse__headline';
			headline.textContent =
				config.labels.headline || config.labels.activity;
			copy.appendChild( headline );
		}
		const link = document.createElement( 'a' );
		link.className = 'voidpulse__message';
		link.href = event.product_url;
		link.textContent = event.message;
		link.addEventListener( 'click', function () {
			track( 'clicks' );
		} );
		copy.appendChild( link );
		node.appendChild( copy );

		if ( config.style.show_close ) {
			const close = document.createElement( 'button' );
			close.type = 'button';
			close.className = 'voidpulse__close';
			close.setAttribute( 'aria-label', config.labels.close );
			close.textContent = '×';
			close.addEventListener( 'click', function () {
				track( 'dismissals' );
				delivery.snooze();
				clearNotification( node );
			} );
			node.appendChild( close );
		}

		document.body.appendChild( node );
		currentNode = node;
		requestAnimationFrame( function () {
			node.classList.add( 'voidpulse--visible' );
		} );
		track( 'impressions' );
		delivery.recordDisplay(
			event.event_key,
			config.timing.duration,
			config.timing.gap
		);
		pauseAutoDismiss( node, config.timing.duration, function () {
			clearNotification( node );
		} );
	}

	function start() {
		fetch( config.rest + 'events', { credentials: 'same-origin' } )
			.then( function ( response ) {
				return response.ok ? response.json() : { events: [] };
			} )
			.then( function ( data ) {
				events = Array.isArray( data.events ) ? data.events : [];
				eventToken = typeof data.token === 'string' ? data.token : '';
				if ( events.length ) {
					schedule(
						showNext,
						delivery.initialDelay( config.timing.initial_delay )
					);
				}
			} )
			.catch( function () {} );
	}

	const mobileQuery = window.matchMedia(
		'(max-width: ' + MOBILE_MAX_WIDTH_PX + 'px)'
	);
	function onViewportChange() {
		if (
			currentNode &&
			isHiddenForViewport( config.display || {}, isMobileViewport() )
		) {
			clearNotification( currentNode );
			return;
		}
		if (
			! currentNode &&
			events.length &&
			! document.querySelector( '.voidpulse' ) &&
			shown < config.timing.maximum
		) {
			schedule( showNext, 1 );
		}
	}
	if ( typeof mobileQuery.addEventListener === 'function' ) {
		mobileQuery.addEventListener( 'change', onViewportChange );
	} else if ( typeof mobileQuery.addListener === 'function' ) {
		mobileQuery.addListener( onViewportChange );
	}
	document.addEventListener( 'visibilitychange', function () {
		if (
			! document.hidden &&
			config.timing.pause_hidden &&
			events.length &&
			! document.querySelector( '.voidpulse' ) &&
			shown < config.timing.maximum
		) {
			schedule( showNext, 1 );
		}
	} );
	window.addEventListener( 'pagehide', function () {
		stopped = true;
		timers.forEach( window.clearTimeout );
		document
			.querySelectorAll( '.voidpulse' )
			.forEach( ( node ) => node.remove() );
	} );
	window.addEventListener( 'pageshow', function ( event ) {
		if ( event.persisted && events.length ) {
			stopped = false;
			schedule(
				showNext,
				delivery.initialDelay( config.timing.initial_delay )
			);
		}
	} );

	if ( 'loading' === document.readyState ) {
		document.addEventListener(
			'DOMContentLoaded',
			function () {
				delivery.defer( start );
			},
			{ once: true }
		);
	} else {
		delivery.defer( start );
	}
} )();
