/* eslint-disable camelcase */
/**
 * Placement definition.
 *
 * @typedef ParallaxPlacement
 * @type {Object.<string, Object>}
 *
 * @property {boolean} enabled      - Whether the placement is enabled.
 * @property {Object}  height       - Height of the parallax placement.
 * @property {number}  height.value - Height value.
 * @property {string}  height.unit  - Height unit.
 */

/**
 * Associative array with parallax options.
 *
 * @typedef ParallaxOptions
 * @type {Object}
 *
 * @property {ParallaxPlacement[]} placements        - Parallax placements.
 *
 * @property {Array}               classes           - Parallax classes.
 * @property {string}              classes.prefix    - Current frontend prefix.
 * @property {string}              classes.container - Parallax container class prefix.
 * @property {string}              classes.clip      - Parallax clip div class.
 * @property {string}              classes.inner     - Parallax inner div class.
 */
(
	() => {
		// The current viewport height. Re-assign on window.resize event.
		let viewportHeight;

		// Object to save placement keys that have been initialized.
		const initializedPlacements = {};

		// Object to save initialized image instances.
		const imageInstances = {};

		// Test via a getter in the options object to see if the passive property is accessed
		let supportsPassive = false;
		try {
			const opts = Object.defineProperty( {}, 'passive', {
				get: () => {
					supportsPassive = { passive: true };
				},
			} );
			window.addEventListener( 'testPassive', null, opts );
			window.removeEventListener( 'testPassive', null, opts );
		} catch ( e ) {}

		/** @type {ParallaxOptions} see type definition at top of file */
		const options = window.advads_parallax_placement_options;

		/**
		 * Set styles on the inner container when the parallax placement gets initialized and on window.resize.
		 *
		 * @param {Element} placementContainer
		 * @param {Element} placementInner
		 */
		const onLoad = ( placementContainer, placementInner ) => {
			placementInner.style.maxWidth =
				placementContainer.offsetWidth + 'px';
			placementInner.style.visibility = 'visible';

			viewportHeight = window.innerHeight;
		};

		/**
		 * Iterate all parallax placements. If a placement is found, call the passed callback.
		 *
		 * @param {Function}            callback
		 * @param {ParallaxPlacement[]} placements
		 */
		const calculate = ( callback, placements ) => {
			for ( const placementsKey in placements ) {
				const placementContainer =
					document.getElementById(
						options.classes.prefix + options.classes.container + placementsKey
					) ||
					document.querySelector(
						'.' + options.classes.prefix + 'parallax-container[data-parallax-id="' + placementsKey + '"]'
					);

				if ( placementContainer === null ) {
					continue;
				}
				const placementInner =
					placementContainer.getElementsByClassName(
						options.classes.prefix + options.classes.inner
					)[ 0 ];

				if ( placementContainer && placementInner ) {
					initializedPlacements[ placementsKey ] = true;
					callback(
						placementContainer,
						placementInner,
						options.placements[ placementsKey ]
					);
				}
			}
		};

		/**
		 * Fit the parallax image into the container.
		 *
		 * @param {Element} placementContainer
		 * @param {Element} placementInner
		 * @param {string}  event
		 */
		const fitImage = ( placementContainer, placementInner, event = '' ) => {
			const imgElement = placementInner.querySelector( 'img' );
			if ( ! imgElement ) {
				return false;
			}

			if ( ! imageInstances[ imgElement.src ] ) {
				imageInstances[ imgElement.src ] = {
					// eslint-disable-next-line no-undef
					image: new Image(),
					isLoaded: false,
				};
			}
			const containerRect = placementContainer.getBoundingClientRect();
			const resizeCallback = ( img ) => {
				if (
					img.naturalHeight / img.naturalWidth >
					viewportHeight / placementContainer.clientWidth
				) {
					imgElement.style.objectFit = 'contain';
				}

				placementInner.style.left =
					containerRect.width / 2 + containerRect.left + 'px';

				if (
					img.naturalHeight >= viewportHeight &&
					img.naturalWidth <= placementContainer.clientWidth
				) {
					imgElement.style.height = '100vh';
					placementInner.style.transform = 'translateX(-50%)';
					return;
				}

				if (
					placementInner.getBoundingClientRect().height <
					containerRect.height
				) {
					placementInner.style.height = containerRect.height + 'px';
					imgElement.style.objectFit = 'cover';
				}
			};
			const scrollCallback = () => {
				let offsetY;

				if (
					containerRect.bottom >= viewportHeight &&
					containerRect.top <= viewportHeight
				) {
					offsetY =
						viewportHeight -
						placementInner.getBoundingClientRect().height;
				} else if (
					containerRect.top <= 0 &&
					containerRect.bottom > 0
				) {
					offsetY = 0;
				} else {
					offsetY = (
						( ( viewportHeight -
							placementInner.getBoundingClientRect().height ) /
							( viewportHeight - containerRect.height ) ) *
						containerRect.top
					).toFixed( 2 );
				}

				placementInner.style.transform =
					'translate3d(-50%,' + offsetY + 'px, 0)';
			};

			if ( imageInstances[ imgElement.src ].isLoaded ) {
				if ( event !== 'scroll' ) {
					resizeCallback( imageInstances[ imgElement.src ].image );
				}

				scrollCallback();
				return;
			}

			imageInstances[ imgElement.src ].image.addEventListener(
				'load',
				( e ) => {
					imageInstances[ imgElement.src ].isLoaded =
						e.target.complete && e.target.naturalHeight !== 0;

					resizeCallback( e.target );
					scrollCallback();
				}
			);

			imageInstances[ imgElement.src ].image.src = imgElement.src;
		};

		/**
		 * Initialize Placements.
		 * Use this on page load and as a callback for deferred injection.
		 */
		const initializePlacements = () => {
			const placements = Object.assign( {}, options.placements );
			for ( const placementsKey in initializedPlacements ) {
				delete placements[ placementsKey ];
			}

			if ( ! Object.keys( placements ).length ) {
				return;
			}

			calculate( ( placementContainer, placementInner ) => {
				placementContainer.style.visibility = 'hidden';

				onLoad( placementContainer, placementInner );
				fitImage( placementContainer, placementInner );

				placementContainer.style.visibility = 'visible';
			}, placements );
		};

		// register event listeners and initialize existing parallax placements.
		initializePlacements();
		document.addEventListener(
			'DOMContentLoaded',
			initializePlacements,
			supportsPassive
		);

		window.addEventListener(
			'resize',
			() =>
				calculate( ( placementContainer, placementInner ) => {
					onLoad( placementContainer, placementInner );
					fitImage( placementContainer, placementInner, 'resize' );
				}, options.placements ),
			supportsPassive
		);

		const fitImageOnScroll = () =>
			calculate( ( placementContainer, placementInner ) => {
				fitImage( placementContainer, placementInner, 'scroll' );
			}, options.placements );

		let ticking = false;
		const onScroll = () => {
			if ( ! ticking ) {
				window.requestAnimationFrame( () => {
					fitImageOnScroll();
					ticking = false;
				} );
				ticking = true;
			}
		};

		window.addEventListener( 'scroll', onScroll, supportsPassive );
		window.addEventListener( 'touchmove', onScroll, supportsPassive );

		// add cache-busting event listeners.
		if (
			typeof advanced_ads_pro !== 'undefined' &&
			typeof advanced_ads_pro.observers !== 'undefined'
		) {
			advanced_ads_pro.observers.add( ( event ) => {
				if (
					[ 'inject_passive_ads', 'inject_ajax_ads' ].indexOf(
						event.event
					) === -1 ||
					( event.ad_ids && ! Object.keys( event.ad_ids ).length )
				) {
					return;
				}
				initializePlacements();
			} );
		}
	}
)();
