import ctEvents from 'ct-events'
import { isTouchDevice } from './frontend/helpers/is-touch-device'
import { isIosDevice } from './frontend/helpers/is-ios-device'
import $ from 'jquery'

const loadSingleEntryPoint = ({
	els,
	events,
	forcedEvents,
	load,
	mount,
	condition,
	trigger
}) => {
	if (!els) {
		els = []
	}

	if (!events) {
		events = []
	}

	if (!forcedEvents) {
		forcedEvents = []
	}

	if (!trigger) {
		trigger = []
	}

	/*
	[
		{
			id: 'click',
		},
	]
    */
	trigger = trigger.map((t) => {
		if (typeof t === 'string') {
			return {
				id: t
			}
		}

		return t
	})

	if (!mount) {
		mount = ({ mount, el, ...everything }) =>
			el ? mount(el, everything) : mount()
	}

	if (els && {}.toString.call(els) === '[object Function]') {
		els = els()
	}

	const allEls = (Array.isArray(els) ? els : [els]).reduce(
		(a, selector) => [
			...a,
			...(Array.isArray(selector)
				? selector
				: typeof selector === 'string'
					? document.querySelectorAll(selector)
					: [selector])
		],
		[]
	)

	if (allEls.length === 0) {
		return
	}

	if (
		condition &&
		!condition({
			els,
			allEls
		})
	) {
		return
	}

	if (trigger.length === 0) {
		load().then((arg) => {
			allEls.map((el) => {
				mount({ ...arg, el })
			})
		})

		return
	}

	trigger.forEach((triggerDescriptor) => {
		if (triggerDescriptor.id === 'click') {
			allEls.map((el) => {
				if (el.hasLazyLoadClickListener) {
					return
				}

				el.hasLazyLoadClickListener = true

				el.addEventListener(
					'click',
					(event) => {
						// stopPropagation(). is here because on touch devices the
						// click event gets triggered for every child in the
						// actual el.
						//
						// In result, mount is triggered a couple times.
						//
						// Context: https://github.com/creative-themes/blocksy/issues/3374
						event.stopPropagation()

						event.preventDefault()

						load().then((arg) => mount({ ...arg, event, el }))
					},
					{
						...(triggerDescriptor.once
							? {
									once: true
								}
							: {})
					}
				)
			})
		}

		if (triggerDescriptor.id === 'change') {
			allEls.map((el) => {
				if (el.hasLazyLoadChangeListener) {
					return
				}

				el.hasLazyLoadChangeListener = true

				const cb = (event) => {
					event.preventDefault()
					load().then((arg) => mount({ ...arg, event, el }))
				}

				if ($) {
					$(el).on('change', cb)
				} else {
					el.addEventListener('change', cb)
				}
			})
		}

		if (triggerDescriptor.id === 'scroll') {
			allEls.map((el) => {
				if (el.hasLazyLoadScrollListener) {
					return
				}

				el.hasLazyLoadScrollListener = true

				setTimeout(() => {
					let prevScroll = scrollY

					let cb = (event) => {
						// If the element was removed from the DOM before the
						// threshold was reached, we should remove the listener
						// to prevent memory leaks and to prevent calling the
						// mount on element being outside of DOM.
						if (!el.parentNode) {
							document.removeEventListener('scroll', cb)
							return
						}

						if (Math.abs(scrollY - prevScroll) > 30) {
							document.removeEventListener('scroll', cb)

							load().then((arg) => {
								return mount({ ...arg, event, el })
							})

							return
						}
					}

					document.addEventListener('scroll', cb)
				}, 500)
			})
		}

		if (triggerDescriptor.id === 'slight-mousemove') {
			if (!document.body.hasSlightMousemoveListenerTheme) {
				document.body.hasSlightMousemoveListenerTheme = true

				const cb = (event) => {
					allEls.map((el) => {
						load().then((arg) => mount({ ...arg, el }))
					})
				}

				document.addEventListener('mousemove', cb, { once: true })
				// document.addEventListener('touchstart', cb, { once: true })
			}
		}

		if (triggerDescriptor.id === 'input') {
			allEls.map((el) => {
				if (el.hasLazyLoadInputListener) {
					return
				}

				el.hasLazyLoadInputListener = true

				el.addEventListener(
					'input',
					(event) => load().then((arg) => mount({ ...arg, el })),
					{ once: true }
				)
			})
		}

		if (triggerDescriptor.id === 'hover-with-touch') {
			allEls.map((el) => {
				if (el.dataset.autoplay && parseFloat(el.dataset.autoplay)) {
					const elRect = el.getBoundingClientRect()

					if (
						// Ensure element is visible
						elRect.width > 0 &&
						!el.hasLazyLoadMouseOverAutoplayListener
					) {
						el.hasLazyLoadMouseOverAutoplayListener = true

						setTimeout(() => {
							load().then((arg) =>
								mount({
									...arg,
									el
								})
							)
						}, 10)
					}

					return
				}

				if (el.hasLazyLoadMouseOverListener) {
					return
				}

				el.hasLazyLoadMouseOverListener = true

				el.forcedMount = (data = {}) =>
					load().then((arg) => mount({ ...arg, el, ...data }))
				;['mouseover', ...(isTouchDevice() ? ['touchstart'] : [])].map(
					(eventToRegister) => {
						el.addEventListener(
							eventToRegister,
							(event) => {
								if (event.type === 'touchstart') {
									document.addEventListener(
										'touchmove',
										() => {
											el.forcedMount({
												event
											})
										},
										{
											once: true
										}
									)
								} else {
									el.forcedMount({
										event
									})
								}
							},
							{ once: true, passive: true }
						)
					}
				)
			})
		}

		if (triggerDescriptor.id === 'hover-with-click') {
			allEls.map((el) => {
				if (el.hasLazyLoadClickHoverListener) {
					return
				}

				el.hasLazyLoadClickHoverListener = true

				const l = (event) => {
					load().then((arg) =>
						mount({
							...arg,
							event,
							el
						})
					)
				}

				// Only relevant for touch devices
				//
				// false | number | true
				let mouseOverState = false

				const isIgnored = (event) => {
					if (triggerDescriptor.ignoredEls) {
						return triggerDescriptor.ignoredEls.some((selector) => {
							return (
								event.target.closest(selector) ||
								event.target.matches(selector)
							)
						})
					}

					return false
				}

				const controller = new AbortController()

				el.addEventListener(
					'mouseover',
					(event) => {
						if (isIgnored(event)) {
							return
						}

						// Add delay to wait for potential click event
						// This should be done only on touch devices to facilitate
						// devices that have both touch and pointin capabilities.
						//
						// Need to wait 500ms specifically because that is how
						// much time is passing between mouseover and a full
						// click event.
						if (isTouchDevice()) {
							mouseOverState = setTimeout(() => {
								mouseOverState = true
								l(event)

								controller.abort()
							}, 500)
						}

						// Non touch device gets processed immediately, to not
						// make it wait.
						if (!isTouchDevice()) {
							controller.abort()

							l(event)
						}
					},
					{
						signal: controller.signal
					}
				)

				if (isTouchDevice()) {
					el.addEventListener(
						'click',
						(event) => {
							if (isIgnored(event)) {
								return
							}

							controller.abort()

							// Previously, iOS devices were handling such
							// behavior out of the box, but now it is
							// mandatory to prevent the default behavior of the
							// click event even there.
							event.preventDefault()

							if (mouseOverState === true) {
								return
							}

							if (mouseOverState !== false) {
								clearTimeout(mouseOverState)
							}

							l(event)
						},
						{
							signal: controller.signal
						}
					)
				}

				el.addEventListener('focus', l, { once: true })
			})
		}

		if (triggerDescriptor.id === 'hover') {
			allEls.map((el) => {
				if (el.hasLazyLoadMouseOverListener) {
					return
				}

				el.hasLazyLoadHoverListener = true

				el.addEventListener(
					'mouseover',
					(event) => {
						load().then((arg) =>
							mount({
								...arg,
								event,
								el
							})
						)
					},
					{ once: true }
				)
			})
		}

		if (triggerDescriptor.id === 'submit') {
			allEls.map((el) => {
				if (el.hasLazyLoadSubmitListener) {
					return
				}

				el.hasLazyLoadSubmitListener = true

				el.addEventListener('submit', (event) => {
					if (
						event.submitter &&
						triggerDescriptor.ignoreSubmitter &&
						triggerDescriptor.ignoreSubmitter.find((selector) =>
							event.submitter.matches(selector)
						)
					) {
						return
					}

					event.preventDefault()
					load().then((arg) => mount({ ...arg, event, el }))
				})
			})
		}
	})
}

export const onDocumentLoaded = (cb) => {
	if (/comp|inter|loaded/.test(document.readyState)) {
		cb()
	} else {
		document.addEventListener('DOMContentLoaded', cb, false)
	}
}

export const handleEntryPoints = (mountEntryPoints, args) => {
	const { immediate = false, skipEvents = false } = args || {}

	if (!skipEvents) {
		;[
			...new Set(
				mountEntryPoints.reduce(
					(currentEvents, entry) => [
						...currentEvents,
						...(entry.events || []),
						...(entry.forcedEvents || [])
					],
					[]
				)
			)
		].map((distinctEvent) => {
			ctEvents.on(distinctEvent, () => {
				mountEntryPoints
					.filter(
						({ events = [] }) => events.indexOf(distinctEvent) > -1
					)
					.map((c) => loadSingleEntryPoint({ ...c, trigger: [] }))

				mountEntryPoints
					.filter(
						({ forcedEvents = [] }) =>
							forcedEvents.indexOf(distinctEvent) > -1
					)
					.map((entry) =>
						loadSingleEntryPoint({
							...entry,
							...(entry.forcedEventsElsSkip
								? {}
								: {
										els: ['body']
									}),
							condition: () => true,
							trigger: []
						})
					)
			})
		})
	}

	const loadInitialEntryPoints = () => {
		mountEntryPoints
			.filter(({ onLoad = true }) => {
				if ({}.toString.call(onLoad) === '[object Function]') {
					return onLoad()
				}

				return !!onLoad
			})
			.map(loadSingleEntryPoint)
	}

	if (immediate) {
		loadInitialEntryPoints()
	} else {
		onDocumentLoaded(loadInitialEntryPoints)
	}
}

var loadCSS = function (href, before, media, attributes) {
	var doc = document
	var ss = doc.createElement('link')
	var ref

	if (before) {
		ref = before
	} else {
		var refs = (doc.body || doc.getElementsByTagName('head')[0]).childNodes
		ref = refs[refs.length - 1]
	}

	var sheets = doc.styleSheets
	ss.rel = 'stylesheet'
	ss.href = href
	// ss.media = 'only x'

	// ref.parentNode.insertBefore(ss, before ? ref : ref.nextSibling)
	document.body.appendChild(ss)

	var onloadcssdefined = function (cb) {
		var resolvedHref = ss.href
		var i = sheets.length
		while (i--) {
			if (sheets[i].href === resolvedHref) {
				return cb()
			}
		}
		setTimeout(function () {
			onloadcssdefined(cb)
		})
	}

	function loadCB() {
		if (ss.addEventListener) {
			ss.removeEventListener('load', loadCB)
		}
		// ss.media = media || 'all'
	}

	if (ss.addEventListener) {
		ss.addEventListener('load', loadCB)
	}
	ss.onloadcssdefined = onloadcssdefined
	onloadcssdefined(loadCB)
	return ss
}

function onloadCSS(ss, callback) {
	var called

	function newcb() {
		if (!called && callback) {
			called = true
			callback.call(ss)
		}
	}

	if (ss.addEventListener) {
		ss.addEventListener('load', newcb)
	}

	if (ss.attachEvent) {
		ss.attachEvent('onload', newcb)
	}

	if ('isApplicationInstalled' in navigator && 'onloadcssdefined' in ss) {
		ss.onloadcssdefined(newcb)
	}
}

const loadedStyles = {}

export const loadStyle = (src, hasDisable = false) =>
	new Promise((resolve, reject) => {
		if (loadedStyles[src] || document.querySelector(`[href="${src}"]`)) {
			loadedStyles[src] = true
			resolve()
			return
		}

		requestAnimationFrame(() => {
			const ss = loadCSS(src)

			onloadCSS(ss, () => {
				loadedStyles[src] = true

				requestAnimationFrame(() => {
					resolve()
				})
			})
		})
	})

// Preload lazily-loaded stylesheets that match selectors found in the
// given content. Accepts an HTML string or a DOM element.
export const preloadAssetsForContent = (content) => {
	if (!content || !ct_localizations.dynamic_styles_selectors) {
		return Promise.resolve()
	}

	let el

	if (typeof content === 'string') {
		el = document.createElement('div')
		el.innerHTML = content
	} else {
		el = content
	}

	const promises = ct_localizations.dynamic_styles_selectors
		.filter(
			(styleDescriptor) =>
				!loadedStyles[styleDescriptor.url] &&
				(el.matches(styleDescriptor.selector) ||
					el.querySelector(styleDescriptor.selector))
		)
		.map((s) => loadStyle(s.url))

	return promises.length > 0 ? Promise.all(promises) : Promise.resolve()
}
