/*
 * File: iframeResizer.contentWindow.js
 * Desc: Include this file in any page being loaded into an iframe
 *       to force the iframe to resize to the content size.
 * Requires: iframeResizer.js on host page.
 * Doc: https://github.com/davidjbradshaw/iframe-resizer
 * Author: David J. Bradshaw - dave@bradshaw.net
 *
 */

// eslint-disable-next-line sonarjs/cognitive-complexity, no-shadow-restricted-names
wp.domReady( function () {
	( function ( undefined ) {
		if ( typeof window === 'undefined' ) return; // don't run for server side render

		let autoResize = true,
			base = 10,
			bodyBackground = '',
			bodyMargin = 0,
			bodyMarginStr = '',
			bodyObserver = null,
			bodyPadding = '',
			calculateWidth = false,
			doubleEventList = { resize: 1, click: 1 },
			eventCancelTimer = 128,
			firstRun = true,
			height = 1,
			heightCalcModeDefault = 'bodyOffset',
			heightCalcMode = heightCalcModeDefault,
			initLock = true,
			initMsg = '',
			inPageLinks = {},
			interval = 32,
			intervalTimer = null,
			logging = false,
			mouseEvents = false,
			msgID = '[iFrameSizer]', // Must match host page msg ID
			msgIdLen = msgID.length,
			myID = '',
			resetRequiredMethods = {
				max: 1,
				min: 1,
				bodyScroll: 1,
				documentElementScroll: 1,
			},
			resizeFrom = 'child',
			sendPermit = true,
			target = window.parent,
			targetOriginDefault = '*',
			tolerance = 0,
			triggerLocked = false,
			triggerLockedTimer = null,
			throttledTimer = 16,
			width = 1,
			widthCalcModeDefault = 'scroll',
			widthCalcMode = widthCalcModeDefault,
			win = window,
			onMessage = function () {
				warn( 'onMessage function not defined' );
			},
			onReady = function () {},
			onPageInfo = function () {},
			customCalcMethods = {
				height() {
					warn( 'Custom height calculation function not defined' );
					return document.documentElement.offsetHeight;
				},
				width() {
					warn( 'Custom width calculation function not defined' );
					return document.body.scrollWidth;
				},
			},
			eventHandlersByName = {},
			passiveSupported = false;

		function noop() {}

		try {
			const options = Object.create(
				{},
				{
					passive: {
						get() {
							// eslint-disable-line getter-return
							passiveSupported = true;
						},
					},
				}
			);
			window.addEventListener( 'test', noop, options );
			window.removeEventListener( 'test', noop, options );
		} catch ( error ) {
			/* */
		}

		function addEventListener( el, evt, func, options ) {
			el.addEventListener(
				evt,
				func,
				passiveSupported ? options || {} : false
			);
		}

		function removeEventListener( el, evt, func ) {
			el.removeEventListener( evt, func, false );
		}

		function capitalizeFirstLetter( string ) {
			return string.charAt( 0 ).toUpperCase() + string.slice( 1 );
		}

		// Based on underscore.js
		function throttle( func ) {
			let context,
				args,
				result,
				timeout = null,
				previous = 0,
				later = function () {
					previous = Date.now();
					timeout = null;
					result = func.apply( context, args );
					if ( ! timeout ) {
						// eslint-disable-next-line no-multi-assign
						context = args = null;
					}
				};

			return function () {
				const now = Date.now();

				if ( ! previous ) {
					previous = now;
				}

				const remaining = throttledTimer - ( now - previous );

				context = this;
				args = arguments;

				if ( remaining <= 0 || remaining > throttledTimer ) {
					if ( timeout ) {
						clearTimeout( timeout );
						timeout = null;
					}

					previous = now;
					result = func.apply( context, args );

					if ( ! timeout ) {
						// eslint-disable-next-line no-multi-assign
						context = args = null;
					}
				} else if ( ! timeout ) {
					timeout = setTimeout( later, remaining );
				}

				return result;
			};
		}

		function formatLogMsg( msg ) {
			return msgID + '[' + myID + '] ' + msg;
		}

		function log( msg ) {
			if ( logging && 'object' === typeof window.console ) {
				// eslint-disable-next-line no-console
				console.log( formatLogMsg( msg ) );
			}
		}

		function warn( msg ) {
			if ( 'object' === typeof window.console ) {
				// eslint-disable-next-line no-console
				console.warn( formatLogMsg( msg ) );
			}
		}

		function init() {
			readDataFromParent();
			log( 'Initialising iFrame (' + window.location.href + ')' );
			readDataFromPage();
			setMargin();
			setBodyStyle( 'background', bodyBackground );
			setBodyStyle( 'padding', bodyPadding );
			injectClearFixIntoBodyElement();
			checkHeightMode();
			checkWidthMode();
			stopInfiniteResizingOfIFrame();
			setupPublicMethods();
			setupMouseEvents();
			startEventListeners();
			inPageLinks = setupInPageLinks();
			sendSize( 'init', 'Init message from host page' );
			onReady();
		}

		function readDataFromParent() {
			function strBool( str ) {
				return 'true' === str;
			}

			const data = initMsg.slice( msgIdLen ).split( ':' );

			myID = data[ 0 ];
			bodyMargin =
				undefined === data[ 1 ] ? bodyMargin : Number( data[ 1 ] ); // For V1 compatibility
			calculateWidth =
				undefined === data[ 2 ] ? calculateWidth : strBool( data[ 2 ] );
			logging = undefined === data[ 3 ] ? logging : strBool( data[ 3 ] );
			interval = undefined === data[ 4 ] ? interval : Number( data[ 4 ] );
			autoResize =
				undefined === data[ 6 ] ? autoResize : strBool( data[ 6 ] );
			bodyMarginStr = data[ 7 ];
			heightCalcMode =
				undefined === data[ 8 ] ? heightCalcMode : data[ 8 ];
			bodyBackground = data[ 9 ];
			bodyPadding = data[ 10 ];
			tolerance =
				undefined === data[ 11 ] ? tolerance : Number( data[ 11 ] );
			inPageLinks.enable =
				undefined === data[ 12 ] ? false : strBool( data[ 12 ] );
			resizeFrom = undefined === data[ 13 ] ? resizeFrom : data[ 13 ];
			widthCalcMode =
				undefined === data[ 14 ] ? widthCalcMode : data[ 14 ];
			mouseEvents =
				undefined === data[ 15 ] ? mouseEvents : strBool( data[ 15 ] );
		}

		function depricate( key ) {
			const splitName = key.split( 'Callback' );

			if ( splitName.length === 2 ) {
				const name =
					'on' +
					splitName[ 0 ].charAt( 0 ).toUpperCase() +
					splitName[ 0 ].slice( 1 );
				this[ name ] = this[ key ];
				delete this[ key ];
				warn(
					"Deprecated: '" +
						key +
						"' has been renamed '" +
						name +
						"'. The old method will be removed in the next major version."
				);
			}
		}

		function readDataFromPage() {
			function readData() {
				const data = window.iFrameResizer;

				log( 'Reading data from page: ' + JSON.stringify( data ) );
				Object.keys( data ).forEach( depricate, data );

				onMessage = 'onMessage' in data ? data.onMessage : onMessage;
				onReady = 'onReady' in data ? data.onReady : onReady;
				targetOriginDefault =
					'targetOrigin' in data
						? data.targetOrigin
						: targetOriginDefault;
				heightCalcMode =
					'heightCalculationMethod' in data
						? data.heightCalculationMethod
						: heightCalcMode;
				widthCalcMode =
					'widthCalculationMethod' in data
						? data.widthCalculationMethod
						: widthCalcMode;
			}

			function setupCustomCalcMethods( calcMode, calcFunc ) {
				if ( 'function' === typeof calcMode ) {
					log( 'Setup custom ' + calcFunc + 'CalcMethod' );
					customCalcMethods[ calcFunc ] = calcMode;
					calcMode = 'custom';
				}

				return calcMode;
			}

			if (
				'iFrameResizer' in window &&
				Object === window.iFrameResizer.constructor
			) {
				readData();
				heightCalcMode = setupCustomCalcMethods(
					heightCalcMode,
					'height'
				);
				widthCalcMode = setupCustomCalcMethods(
					widthCalcMode,
					'width'
				);
			}

			log( 'TargetOrigin for parent set to: ' + targetOriginDefault );
		}

		function chkCSS( attr, value ) {
			if ( -1 !== value.indexOf( '-' ) ) {
				warn( 'Negative CSS value ignored for ' + attr );
				value = '';
			}
			return value;
		}

		function setBodyStyle( attr, value ) {
			if ( undefined !== value && '' !== value && 'null' !== value ) {
				document.body.style[ attr ] = value;
				log( 'Body ' + attr + ' set to "' + value + '"' );
			}
		}

		function setMargin() {
			// If called via V1 script, convert bodyMargin from int to str
			if ( undefined === bodyMarginStr ) {
				bodyMarginStr = bodyMargin + 'px';
			}

			setBodyStyle( 'margin', chkCSS( 'margin', bodyMarginStr ) );
		}

		function stopInfiniteResizingOfIFrame() {
			document.documentElement.style.height = '';
			document.body.style.height = '';
			log( 'HTML & body height set to "auto"' );
		}

		function manageTriggerEvent( options ) {
			const listener = {
				add( eventName ) {
					function handleEvent() {
						sendSize( options.eventName, options.eventType );
					}

					eventHandlersByName[ eventName ] = handleEvent;

					addEventListener( window, eventName, handleEvent, {
						passive: true,
					} );
				},
				remove( eventName ) {
					const handleEvent = eventHandlersByName[ eventName ];
					delete eventHandlersByName[ eventName ];

					removeEventListener( window, eventName, handleEvent );
				},
			};

			if ( options.eventNames && Array.prototype.map ) {
				options.eventName = options.eventNames[ 0 ];
				options.eventNames.map( listener[ options.method ] );
			} else {
				listener[ options.method ]( options.eventName );
			}

			log(
				capitalizeFirstLetter( options.method ) +
					' event listener: ' +
					options.eventType
			);
		}

		function manageEventListeners( method ) {
			manageTriggerEvent( {
				method,
				eventType: 'Animation Start',
				eventNames: [ 'animationstart', 'webkitAnimationStart' ],
			} );
			manageTriggerEvent( {
				method,
				eventType: 'Animation Iteration',
				eventNames: [
					'animationiteration',
					'webkitAnimationIteration',
				],
			} );
			manageTriggerEvent( {
				method,
				eventType: 'Animation End',
				eventNames: [ 'animationend', 'webkitAnimationEnd' ],
			} );
			manageTriggerEvent( {
				method,
				eventType: 'Input',
				eventName: 'input',
			} );
			manageTriggerEvent( {
				method,
				eventType: 'Mouse Up',
				eventName: 'mouseup',
			} );
			manageTriggerEvent( {
				method,
				eventType: 'Mouse Down',
				eventName: 'mousedown',
			} );
			manageTriggerEvent( {
				method,
				eventType: 'Orientation Change',
				eventName: 'orientationchange',
			} );
			manageTriggerEvent( {
				method,
				eventType: 'Print',
				eventNames: [ 'afterprint', 'beforeprint' ],
			} );
			manageTriggerEvent( {
				method,
				eventType: 'Ready State Change',
				eventName: 'readystatechange',
			} );
			manageTriggerEvent( {
				method,
				eventType: 'Touch Start',
				eventName: 'touchstart',
			} );
			manageTriggerEvent( {
				method,
				eventType: 'Touch End',
				eventName: 'touchend',
			} );
			manageTriggerEvent( {
				method,
				eventType: 'Touch Cancel',
				eventName: 'touchcancel',
			} );
			manageTriggerEvent( {
				method,
				eventType: 'Transition Start',
				eventNames: [
					'transitionstart',
					'webkitTransitionStart',
					'MSTransitionStart',
					'oTransitionStart',
					'otransitionstart',
				],
			} );
			manageTriggerEvent( {
				method,
				eventType: 'Transition Iteration',
				eventNames: [
					'transitioniteration',
					'webkitTransitionIteration',
					'MSTransitionIteration',
					'oTransitionIteration',
					'otransitioniteration',
				],
			} );
			manageTriggerEvent( {
				method,
				eventType: 'Transition End',
				eventNames: [
					'transitionend',
					'webkitTransitionEnd',
					'MSTransitionEnd',
					'oTransitionEnd',
					'otransitionend',
				],
			} );
			if ( 'child' === resizeFrom ) {
				manageTriggerEvent( {
					method,
					eventType: 'IFrame Resized',
					eventName: 'resize',
				} );
			}
		}

		function checkCalcMode( calcMode, calcModeDefault, modes, type ) {
			if ( calcModeDefault !== calcMode ) {
				if ( ! ( calcMode in modes ) ) {
					warn(
						calcMode +
							' is not a valid option for ' +
							type +
							'CalculationMethod.'
					);
					calcMode = calcModeDefault;
				}
				log( type + ' calculation method set to "' + calcMode + '"' );
			}

			return calcMode;
		}

		function checkHeightMode() {
			heightCalcMode = checkCalcMode(
				heightCalcMode,
				heightCalcModeDefault,
				getHeight,
				'height'
			);
		}

		function checkWidthMode() {
			widthCalcMode = checkCalcMode(
				widthCalcMode,
				widthCalcModeDefault,
				getWidth,
				'width'
			);
		}

		function startEventListeners() {
			if ( true === autoResize ) {
				manageEventListeners( 'add' );
				setupMutationObserver();
			} else {
				log( 'Auto Resize disabled' );
			}
		}

		//   function stopMsgsToParent() {
		//     log('Disable outgoing messages')
		//     sendPermit = false
		//   }

		//   function removeMsgListener() {
		//     log('Remove event listener: Message')
		//     removeEventListener(window, 'message', receiver)
		//   }

		function disconnectMutationObserver() {
			if ( null !== bodyObserver ) {
				/* istanbul ignore next */ // Not testable in PhantonJS
				bodyObserver.disconnect();
			}
		}

		function stopEventListeners() {
			manageEventListeners( 'remove' );
			disconnectMutationObserver();
			clearInterval( intervalTimer );
		}

		//   function teardown() {
		//     stopMsgsToParent()
		//     removeMsgListener()
		//     if (true === autoResize) stopEventListeners()
		//   }

		function injectClearFixIntoBodyElement() {
			const clearFix = document.createElement( 'div' );
			clearFix.style.clear = 'both';
			// Guard against the following having been globally redefined in CSS.
			clearFix.style.display = 'block';
			clearFix.style.height = '0';
			document.body.appendChild( clearFix );
		}

		function setupInPageLinks() {
			function getPagePosition() {
				return {
					x:
						window.pageXOffset === undefined
							? document.documentElement.scrollLeft
							: window.pageXOffset,
					y:
						window.pageYOffset === undefined
							? document.documentElement.scrollTop
							: window.pageYOffset,
				};
			}

			function getElementPosition( el ) {
				const elPosition = el.getBoundingClientRect(),
					pagePosition = getPagePosition();

				return {
					x:
						parseInt( elPosition.left, 10 ) +
						parseInt( pagePosition.x, 10 ),
					y:
						parseInt( elPosition.top, 10 ) +
						parseInt( pagePosition.y, 10 ),
				};
			}

			function findTarget( location ) {
				function jumpToTarget( target ) {
					const jumpPosition = getElementPosition( target );

					log(
						'Moving to in page link (#' +
							hash +
							') at x: ' +
							jumpPosition.x +
							' y: ' +
							jumpPosition.y
					);
					sendMsg( jumpPosition.y, jumpPosition.x, 'scrollToOffset' ); // X&Y reversed at sendMsg uses height/width
				}

				var hash = location.split( '#' )[ 1 ] || location, // Remove # if present
					hashData = decodeURIComponent( hash ),
					target =
						document.getElementById( hashData ) ||
						document.getElementsByName( hashData )[ 0 ];

				if ( undefined === target ) {
					log(
						'In page link (#' +
							hash +
							') not found in iFrame, so sending to parent'
					);
					sendMsg( 0, 0, 'inPageLink', '#' + hash );
				} else {
					jumpToTarget( target );
				}
			}

			function checkLocationHash() {
				const hash = window.location.hash;
				const href = window.location.href;

				if ( '' !== hash && '#' !== hash ) {
					findTarget( href );
				}
			}

			function bindAnchors() {
				function setupLink( el ) {
					function linkClicked( e ) {
						e.preventDefault();

						/* jshint validthis:true */
						findTarget( this.getAttribute( 'href' ) );
					}

					if ( '#' !== el.getAttribute( 'href' ) ) {
						addEventListener( el, 'click', linkClicked );
					}
				}

				Array.prototype.forEach.call(
					document.querySelectorAll( 'a[href^="#"]' ),
					setupLink
				);
			}

			function bindLocationHash() {
				addEventListener( window, 'hashchange', checkLocationHash );
			}

			function initCheck() {
				// Check if page loaded with location hash after init resize
				setTimeout( checkLocationHash, eventCancelTimer );
			}

			function enableInPageLinks() {
				/* istanbul ignore else */ // Not testable in phantonJS
				if ( Array.prototype.forEach && document.querySelectorAll ) {
					log( 'Setting up location.hash handlers' );
					bindAnchors();
					bindLocationHash();
					initCheck();
				} else {
					warn(
						'In page linking not fully supported in this browser! (See README.md for IE8 workaround)'
					);
				}
			}

			if ( inPageLinks.enable ) {
				enableInPageLinks();
			} else {
				log( 'In page linking not enabled' );
			}

			return {
				findTarget,
			};
		}

		function setupMouseEvents() {
			if ( mouseEvents !== true ) return;

			function sendMouse( e ) {
				sendMsg( 0, 0, e.type, e.screenY + ':' + e.screenX );
			}

			function addMouseListener( evt, name ) {
				log( 'Add event listener: ' + name );
				addEventListener( window.document, evt, sendMouse );
			}

			addMouseListener( 'mouseenter', 'Mouse Enter' );
			addMouseListener( 'mouseleave', 'Mouse Leave' );
		}

		function setupPublicMethods() {
			log( 'Enable public methods' );

			win.parentIFrame = {
				autoResize: function autoResizeF( resize ) {
					if ( true === resize && false === autoResize ) {
						autoResize = true;
						startEventListeners();
					} else if ( false === resize && true === autoResize ) {
						autoResize = false;
						stopEventListeners();
					}
					sendMsg( 0, 0, 'autoResize', JSON.stringify( autoResize ) );
					return autoResize;
				},

				close: function closeF() {
					sendMsg( 0, 0, 'close' );
					// teardown()
				},

				getId: function getIdF() {
					return myID;
				},

				getPageInfo: function getPageInfoF( callback ) {
					if ( 'function' === typeof callback ) {
						onPageInfo = callback;
						sendMsg( 0, 0, 'pageInfo' );
					} else {
						onPageInfo = function () {};
						sendMsg( 0, 0, 'pageInfoStop' );
					}
				},

				moveToAnchor: function moveToAnchorF( hash ) {
					inPageLinks.findTarget( hash );
				},

				reset: function resetF() {
					resetIFrame( 'parentIFrame.reset' );
				},

				scrollTo: function scrollToF( x, y ) {
					sendMsg( y, x, 'scrollTo' ); // X&Y reversed at sendMsg uses height/width
				},

				scrollToOffset: function scrollToF( x, y ) {
					sendMsg( y, x, 'scrollToOffset' ); // X&Y reversed at sendMsg uses height/width
				},

				sendMessage: function sendMessageF( msg, targetOrigin ) {
					sendMsg(
						0,
						0,
						'message',
						JSON.stringify( msg ),
						targetOrigin
					);
				},

				setHeightCalculationMethod:
					function setHeightCalculationMethodF(
						heightCalculationMethod
					) {
						heightCalcMode = heightCalculationMethod;
						checkHeightMode();
					},

				setWidthCalculationMethod: function setWidthCalculationMethodF(
					widthCalculationMethod
				) {
					widthCalcMode = widthCalculationMethod;
					checkWidthMode();
				},

				setTargetOrigin: function setTargetOriginF( targetOrigin ) {
					log( 'Set targetOrigin: ' + targetOrigin );
					targetOriginDefault = targetOrigin;
				},

				size: function sizeF( customHeight, customWidth ) {
					const valString =
						'' +
						( customHeight || '' ) +
						( customWidth ? ',' + customWidth : '' );
					sendSize(
						'size',
						'parentIFrame.size(' + valString + ')',
						customHeight,
						customWidth
					);
				},
			};
		}

		function initInterval() {
			if ( 0 !== interval ) {
				log( 'setInterval: ' + interval + 'ms' );
				intervalTimer = setInterval( function () {
					sendSize( 'interval', 'setInterval: ' + interval );
				}, Math.abs( interval ) );
			}
		}

		// Not testable in PhantomJS
		/* istanbul ignore next */
		function setupBodyMutationObserver() {
			function addImageLoadListners( mutation ) {
				function addImageLoadListener( element ) {
					if ( false === element.complete ) {
						log( 'Attach listeners to ' + element.src );
						element.addEventListener( 'load', imageLoaded, false );
						element.addEventListener( 'error', imageError, false );
						elements.push( element );
					}
				}

				if (
					mutation.type === 'attributes' &&
					mutation.attributeName === 'src'
				) {
					addImageLoadListener( mutation.target );
				} else if ( mutation.type === 'childList' ) {
					Array.prototype.forEach.call(
						mutation.target.querySelectorAll( 'img' ),
						addImageLoadListener
					);
				}
			}

			function removeFromArray( element ) {
				elements.splice( elements.indexOf( element ), 1 );
			}

			function removeImageLoadListener( element ) {
				log( 'Remove listeners from ' + element.src );
				element.removeEventListener( 'load', imageLoaded, false );
				element.removeEventListener( 'error', imageError, false );
				removeFromArray( element );
			}

			function imageEventTriggered( event, type, typeDesc ) {
				removeImageLoadListener( event.target );
				sendSize( type, typeDesc + ': ' + event.target.src );
			}

			function imageLoaded( event ) {
				imageEventTriggered( event, 'imageLoad', 'Image loaded' );
			}

			function imageError( event ) {
				imageEventTriggered(
					event,
					'imageLoadFailed',
					'Image load failed'
				);
			}

			function mutationObserved( mutations ) {
				sendSize(
					'mutationObserver',
					'mutationObserver: ' +
						mutations[ 0 ].target +
						' ' +
						mutations[ 0 ].type
				);

				// Deal with WebKit / Blink asyncing image loading when tags are injected into the page
				mutations.forEach( addImageLoadListners );
			}

			function createMutationObserver() {
				const target = document.querySelector( 'body' ),
					config = {
						attributes: true,
						attributeOldValue: false,
						characterData: true,
						characterDataOldValue: false,
						childList: true,
						subtree: true,
					};

				observer = new MutationObserver( mutationObserved );

				log( 'Create body MutationObserver' );
				observer.observe( target, config );

				return observer;
			}

			var elements = [],
				MutationObserver =
					window.MutationObserver || window.WebKitMutationObserver,
				observer = createMutationObserver();

			return {
				disconnect() {
					if ( 'disconnect' in observer ) {
						log( 'Disconnect body MutationObserver' );
						observer.disconnect();
						elements.forEach( removeImageLoadListener );
					}
				},
			};
		}

		function setupMutationObserver() {
			const forceIntervalTimer = 0 > interval;

			// Not testable in PhantomJS
			/* istanbul ignore if */ if (
				window.MutationObserver ||
				window.WebKitMutationObserver
			) {
				if ( forceIntervalTimer ) {
					initInterval();
				} else {
					bodyObserver = setupBodyMutationObserver();
				}
			} else {
				log( 'MutationObserver not supported in this browser!' );
				initInterval();
			}
		}

		// document.documentElement.offsetHeight is not reliable, so
		// we have to jump through hoops to get a better value.
		function getComputedStyle( prop, el ) {
			let retVal = 0;
			el = el || document.body; // Not testable in phantonJS

			retVal = document.defaultView.getComputedStyle( el, null );
			retVal = null === retVal ? 0 : retVal[ prop ];

			return parseInt( retVal, base );
		}

		function chkEventThottle( timer ) {
			if ( timer > throttledTimer / 2 ) {
				throttledTimer = 2 * timer;
				log( 'Event throttle increased to ' + throttledTimer + 'ms' );
			}
		}

		// Idea from https://github.com/guardian/iframe-messenger
		function getMaxElement( side, elements ) {
			let elementsLength = elements.length,
				elVal = 0,
				maxVal = 0,
				Side = capitalizeFirstLetter( side ),
				timer = Date.now();

			for ( let i = 0; i < elementsLength; i++ ) {
				elVal =
					elements[ i ].getBoundingClientRect()[ side ] +
					getComputedStyle( 'margin' + Side, elements[ i ] );
				if ( elVal > maxVal ) {
					maxVal = elVal;
				}
			}

			timer = Date.now() - timer;

			log( 'Parsed ' + elementsLength + ' HTML elements' );
			log( 'Element position calculated in ' + timer + 'ms' );

			chkEventThottle( timer );

			return maxVal;
		}

		function getAllMeasurements( dimensions ) {
			return [
				dimensions.bodyOffset(),
				dimensions.bodyScroll(),
				dimensions.documentElementOffset(),
				dimensions.documentElementScroll(),
			];
		}

		function getTaggedElements( side, tag ) {
			function noTaggedElementsFound() {
				warn( 'No tagged elements (' + tag + ') found on page' );
				return document.querySelectorAll( 'body *' );
			}

			const elements = document.querySelectorAll( '[' + tag + ']' );

			if ( elements.length === 0 ) noTaggedElementsFound();

			return getMaxElement( side, elements );
		}

		function getAllElements() {
			return document.querySelectorAll( 'body *' );
		}

		var getHeight = {
				bodyOffset: function getBodyOffsetHeight() {
					return (
						document.body.offsetHeight +
						getComputedStyle( 'marginTop' ) +
						getComputedStyle( 'marginBottom' )
					);
				},

				offset() {
					return getHeight.bodyOffset(); // Backwards compatibility
				},

				bodyScroll: function getBodyScrollHeight() {
					return document.body.scrollHeight;
				},

				custom: function getCustomWidth() {
					return customCalcMethods.height();
				},

				documentElementOffset: function getDEOffsetHeight() {
					return document.documentElement.offsetHeight;
				},

				documentElementScroll: function getDEScrollHeight() {
					return document.documentElement.scrollHeight;
				},

				max: function getMaxHeight() {
					return Math.max.apply(
						null,
						getAllMeasurements( getHeight )
					);
				},

				min: function getMinHeight() {
					return Math.min.apply(
						null,
						getAllMeasurements( getHeight )
					);
				},

				grow: function growHeight() {
					return getHeight.max(); // Run max without the forced downsizing
				},

				lowestElement: function getBestHeight() {
					return Math.max(
						getHeight.bodyOffset() ||
							getHeight.documentElementOffset(),
						getMaxElement( 'bottom', getAllElements() )
					);
				},

				taggedElement: function getTaggedElementsHeight() {
					return getTaggedElements( 'bottom', 'data-iframe-height' );
				},
				quillForms: function getQFHeight() {
					if (
						wp.data
							.select( 'quillForms/renderer-core' )
							.isWelcomeScreenActive()
					) {
						return (
							document.querySelector(
								'.qf-welcome-screen-block__content'
							)?.offsetHeight + 60
						);
					}
					if (
						wp.data
							.select( 'quillForms/renderer-core' )
							.isThankyouScreenActive()
					) {
						return (
							document.querySelector(
								'.qf-thankyou-screen-block__content-wrapper'
							)?.offsetHeight + 60
						);
					}
					if (
						wp.data
							.select( 'quillForms/renderer-core' )
							.getPaymentData()
					) {
						return (
							document.querySelector(
								'.renderer-core-payment-modal__content'
							)?.offsetHeight + 60
						);
					}

					return (
						document.querySelector(
							'.renderer-components-field-wrapper.active .renderer-core-block-scroller[data-iframe-height=true] .renderer-components-field-content'
						)?.offsetHeight + 80
					);
				},
			},
			getWidth = {
				bodyScroll: function getBodyScrollWidth() {
					return document.body.scrollWidth;
				},

				bodyOffset: function getBodyOffsetWidth() {
					return document.body.offsetWidth;
				},

				custom: function getCustomWidth() {
					return customCalcMethods.width();
				},

				documentElementScroll: function getDEScrollWidth() {
					return document.documentElement.scrollWidth;
				},

				documentElementOffset: function getDEOffsetWidth() {
					return document.documentElement.offsetWidth;
				},

				scroll: function getMaxWidth() {
					return Math.max(
						getWidth.bodyScroll(),
						getWidth.documentElementScroll()
					);
				},

				max: function getMaxWidth() {
					return Math.max.apply(
						null,
						getAllMeasurements( getWidth )
					);
				},

				min: function getMinWidth() {
					return Math.min.apply(
						null,
						getAllMeasurements( getWidth )
					);
				},

				rightMostElement: function rightMostElement() {
					return getMaxElement( 'right', getAllElements() );
				},

				taggedElement: function getTaggedElementsWidth() {
					return getTaggedElements( 'right', 'data-iframe-width' );
				},
			};

		function sizeIFrame(
			triggerEvent,
			triggerEventDesc,
			customHeight,
			customWidth
		) {
			function resizeIFrame() {
				height = currentHeight;
				width = currentWidth;

				sendMsg( height, width, triggerEvent );
			}

			function isSizeChangeDetected() {
				function checkTolarance( a, b ) {
					const retVal = Math.abs( a - b ) <= tolerance;
					return ! retVal;
				}

				currentHeight =
					undefined === customHeight
						? getHeight[ heightCalcMode ]()
						: customHeight;
				currentWidth =
					undefined === customWidth
						? getWidth[ widthCalcMode ]()
						: customWidth;

				return (
					checkTolarance( height, currentHeight ) ||
					( calculateWidth && checkTolarance( width, currentWidth ) )
				);
			}

			function isForceResizableEvent() {
				return ! ( triggerEvent in { init: 1, interval: 1, size: 1 } );
			}

			function isForceResizableCalcMode() {
				return (
					heightCalcMode in resetRequiredMethods ||
					( calculateWidth && widthCalcMode in resetRequiredMethods )
				);
			}

			function logIgnored() {
				log( 'No change in size detected' );
			}

			function checkDownSizing() {
				if ( isForceResizableEvent() && isForceResizableCalcMode() ) {
					resetIFrame( triggerEventDesc );
				} else if ( ! ( triggerEvent in { interval: 1 } ) ) {
					logIgnored();
				}
			}

			let currentHeight, currentWidth;

			if ( isSizeChangeDetected() || 'init' === triggerEvent ) {
				lockTrigger();
				resizeIFrame();
			} else {
				checkDownSizing();
			}
		}

		const sizeIFrameThrottled = throttle( sizeIFrame );

		function sendSize(
			triggerEvent,
			triggerEventDesc,
			customHeight,
			customWidth
		) {
			function recordTrigger() {
				if (
					! ( triggerEvent in { reset: 1, resetPage: 1, init: 1 } )
				) {
					log( 'Trigger event: ' + triggerEventDesc );
				}
			}

			function isDoubleFiredEvent() {
				return triggerLocked && triggerEvent in doubleEventList;
			}

			if ( isDoubleFiredEvent() ) {
				log( 'Trigger event cancelled: ' + triggerEvent );
			} else {
				recordTrigger();
				if ( triggerEvent === 'init' ) {
					sizeIFrame(
						triggerEvent,
						triggerEventDesc,
						customHeight,
						customWidth
					);
				} else {
					sizeIFrameThrottled(
						triggerEvent,
						triggerEventDesc,
						customHeight,
						customWidth
					);
				}
			}
		}

		function lockTrigger() {
			if ( ! triggerLocked ) {
				triggerLocked = true;
				log( 'Trigger event lock on' );
			}
			clearTimeout( triggerLockedTimer );
			triggerLockedTimer = setTimeout( function () {
				triggerLocked = false;
				log( 'Trigger event lock off' );
				log( '--' );
			}, eventCancelTimer );
		}

		function triggerReset( triggerEvent ) {
			height = getHeight[ heightCalcMode ]();
			width = getWidth[ widthCalcMode ]();

			sendMsg( height, width, triggerEvent );
		}

		function resetIFrame( triggerEventDesc ) {
			const hcm = heightCalcMode;
			heightCalcMode = heightCalcModeDefault;

			log( 'Reset trigger event: ' + triggerEventDesc );
			lockTrigger();
			triggerReset( 'reset' );

			heightCalcMode = hcm;
		}

		function sendMsg( height, width, triggerEvent, msg, targetOrigin ) {
			function setTargetOrigin() {
				if ( undefined === targetOrigin ) {
					targetOrigin = targetOriginDefault;
				} else {
					log( 'Message targetOrigin: ' + targetOrigin );
				}
			}

			function sendToParent() {
				const size = height + ':' + width,
					message =
						myID +
						':' +
						size +
						':' +
						triggerEvent +
						( undefined === msg ? '' : ':' + msg );

				log( 'Sending message to host page (' + message + ')' );
				target.postMessage( msgID + message, targetOrigin );
			}

			if ( true === sendPermit ) {
				setTargetOrigin();
				sendToParent();
			}
		}

		function receiver( event ) {
			const processRequestFromParent = {
				init: function initFromParent() {
					initMsg = event.data;
					target = event.source;

					init();
					firstRun = false;
					setTimeout( function () {
						initLock = false;
					}, eventCancelTimer );
				},

				reset: function resetFromParent() {
					if ( initLock ) {
						log( 'Page reset ignored by init' );
					} else {
						log( 'Page size reset by host page' );
						triggerReset( 'resetPage' );
					}
				},

				resize: function resizeFromParent() {
					sendSize(
						'resizeParent',
						'Parent window requested size check'
					);
				},

				moveToAnchor: function moveToAnchorF() {
					inPageLinks.findTarget( getData() );
				},
				inPageLink: function inPageLinkF() {
					this.moveToAnchor();
				}, // Backward compatibility

				pageInfo: function pageInfoFromParent() {
					const msgBody = getData();
					log( 'PageInfoFromParent called from parent: ' + msgBody );
					onPageInfo( JSON.parse( msgBody ) );
					log( ' --' );
				},

				message: function messageFromParent() {
					const msgBody = getData();

					log( 'onMessage called from parent: ' + msgBody );
					// eslint-disable-next-line sonarjs/no-extra-arguments
					onMessage( JSON.parse( msgBody ) );
					log( ' --' );
				},
			};

			function isMessageForUs() {
				return msgID === ( '' + event.data ).slice( 0, msgIdLen ); // ''+ Protects against non-string messages
			}

			function getMessageType() {
				return event.data.split( ']' )[ 1 ].split( ':' )[ 0 ];
			}

			function getData() {
				return event.data.slice( event.data.indexOf( ':' ) + 1 );
			}

			function isMiddleTier() {
				return (
					( ! ( typeof module !== 'undefined' && module.exports ) &&
						'iFrameResize' in window ) ||
					( window.jQuery !== undefined &&
						'iFrameResize' in window.jQuery.prototype )
				);
			}

			function isInitMsg() {
				// Test if this message is from a child below us. This is an ugly test, however, updating
				// the message format would break backwards compatibility.
				return event.data.split( ':' )[ 2 ] in { true: 1, false: 1 };
			}

			function callFromParent() {
				const messageType = getMessageType();

				if ( messageType in processRequestFromParent ) {
					processRequestFromParent[ messageType ]();
				} else if ( ! isMiddleTier() && ! isInitMsg() ) {
					warn( 'Unexpected message (' + event.data + ')' );
				}
			}

			function processMessage() {
				if ( false === firstRun ) {
					callFromParent();
				} else if ( isInitMsg() ) {
					processRequestFromParent.init();
				} else {
					log(
						'Ignored message of type "' +
							getMessageType() +
							'". Received before initialization.'
					);
				}
			}

			if ( isMessageForUs() ) {
				processMessage();
			}
		}

		// Normally the parent kicks things off when it detects the iFrame has loaded.
		// If this script is async-loaded, then tell parent page to retry init.
		function chkLateLoaded() {
			if ( 'loading' !== document.readyState ) {
				window.parent.postMessage( '[iFrameResizerChild]Ready', '*' );
			}
		}

		addEventListener( window, 'message', receiver );
		addEventListener( window, 'readystatechange', chkLateLoaded );
		chkLateLoaded();
	} )();
} );
