import { scrollLockManager } from './overlay/no-bounce'
import ctEvents from 'ct-events'
import { mount as mountMobileMenu } from './overlay/mobile-menu'

import { focusLockManager } from '../helpers/focus-lock'
import { whenTransitionEnds } from '../helpers/when-transition-ends'
import { isTouchDevice } from '../helpers/is-touch-device'
import { isIosDevice } from '../helpers/is-ios-device'

const persistSettings = (settings) => {
	settings.container.__overlay_settings__ = settings
}

const getSettings = (settings) => {
	if (!settings.container) {
		throw new Error('No container provided')
	}

	return settings.container.__overlay_settings__ || {}
}

const clearSettings = (settings) => {
	settings.container.__overlay_settings__ = null
}

const showOffcanvas = (initialSettings) => {
	const settings = {
		onClose: () => {},
		container: null,
		focus: true,
		...getSettings(initialSettings),
	}
	;[
		...document.querySelectorAll(
			`[data-toggle-panel*="${settings.container.id}"]`
		),

		...document.querySelectorAll(`[href*="${settings.container.id}"]`),
	].map((trigger) => {
		trigger.setAttribute('aria-expanded', 'true')
	})

	if (settings.shouldBeInert) {
		settings.container.inert = false

		// aria-modal should be added only when modal is opened
		// https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-modal
		settings.container.setAttribute('aria-modal', 'true')
	}

	if (settings.focus) {
		setTimeout(() => {
			const maybeInput = settings.container.querySelector('input')

			if (maybeInput) {
				const end = maybeInput.value.length

				maybeInput.setSelectionRange(end, end)
				maybeInput.focus()
			}
		}, 200)
	}

	if (settings.container.querySelector('.ct-panel-content')) {
		settings.container
			.querySelector('.ct-panel-content')
			.addEventListener('click', (event) => {
				Array.from(settings.container.querySelectorAll('select')).map(
					(select) =>
						select.selectr && select.selectr.events.dismiss(event)
				)
			})
	}

	if (
		settings.clickOutside &&
		settings.container.querySelector('.ct-panel-content')
	) {
		settings.container.addEventListener(
			'click',
			settings.handleContainerClick
		)
	}

	const onKeyUp = (event) => {
		const { keyCode, target } = event

		if (keyCode !== 27) return
		event.preventDefault()

		document.body.hasAttribute('data-panel') && hideOffcanvas(settings)

		document.removeEventListener('keyup', onKeyUp)
	}

	document.addEventListener('keyup', onKeyUp)

	let maybeCloseButton =
		settings.container &&
		settings.container.querySelector('.ct-toggle-close')

	if (maybeCloseButton) {
		maybeCloseButton.addEventListener(
			'click',
			(event) => {
				event.preventDefault()
				hideOffcanvas(settings)
			},
			{ once: true }
		)

		if (!maybeCloseButton.hasEnterListener) {
			maybeCloseButton.hasEnterListener = true

			maybeCloseButton.addEventListener('keyup', (e) => {
				if (13 == e.keyCode) {
					e.preventDefault()
					hideOffcanvas(settings)
				}
			})
		}
	}

	if (
		settings.computeScrollContainer ||
		settings.container.querySelector('.ct-panel-content')
	) {
		const scrollContainer = settings.computeScrollContainer
			? settings.computeScrollContainer()
			: settings.container.querySelector('.ct-panel-content')

		scrollLockManager().disable(scrollContainer)

		if (isIosDevice()) {
			const observer = new MutationObserver((mutations) => {
				if (scrollContainer.isConnected) {
					return
				}

				scrollLockManager().enable()

				// If panel is closed, we should not block the scroll
				if (!document.body.hasAttribute('data-panel')) {
					return
				}

				setTimeout(() => {
					scrollLockManager().disable(
						settings.computeScrollContainer
							? settings.computeScrollContainer()
							: settings.container.querySelector(
									'.ct-panel-content'
							  )
					)
				}, 1000)
			})

			observer.observe(settings.container, {
				childList: true,
				subtree: true,
			})

			settings.container.__overlay_observer__ = observer
		}

		setTimeout(() => {
			focusLockManager().focusLockOn(
				settings.container.querySelector('.ct-panel-content')
					.parentNode,
				{
					focusOnMount: !settings.focus,
				}
			)
		})
	}

	/**
	 * Add window event listener in the next frame. This allows us to freely
	 * propagate the current clck event up the chain -- without the modal
	 * getting closed.
	 */
	window.addEventListener('click', settings.handleWindowClick, {
		capture: true,
	})

	ctEvents.trigger('ct:modal:opened', settings.container)
	;[...settings.container.querySelectorAll('.ct-toggle-dropdown-mobile')].map(
		(arrow) => {
			mountMobileMenu(arrow)
		}
	)
}

const hideOffcanvas = (initialSettings, args = {}) => {
	const settings = {
		onClose: () => {},
		container: null,
		...getSettings(initialSettings),
	}

	args = {
		closeInstant: false,
		shouldFocusOriginalTrigger: true,
		...args,
	}

	if (settings.shouldBeInert) {
		settings.container.inert = true

		// aria-modal should be removed when modal is closed
		// https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Attributes/aria-modal
		settings.container.removeAttribute('aria-modal')
	}

	if (!document.body.hasAttribute('data-panel')) {
		settings.container.classList.remove('active')
		settings.onClose()
		return
	}

	;[
		...document.querySelectorAll(
			`[data-toggle-panel*="${settings.container.id}"]`
		),

		...document.querySelectorAll(`[href*="${settings.container.id}"]`),
	].map((trigger, index) => {
		trigger.setAttribute('aria-expanded', 'false')

		if (args.shouldFocusOriginalTrigger && !isTouchDevice()) {
			if (!trigger.focusDisabled) {
				setTimeout(() => {
					if (index === 0) {
						trigger.focus()
					}
				}, 50)
			}

			trigger.focusDisabled = false
		}
	})

	if (args.closeInstant) {
		document.body.removeAttribute('data-panel')
		settings.container.classList.remove('active')

		scrollLockManager().enable(
			settings.computeScrollContainer
				? settings.computeScrollContainer()
				: settings.container.querySelector('.ct-panel-content')
		)

		clearSettings(settings)

		ctEvents.trigger('ct:modal:closed', settings.container)
	} else {
		document.body.dataset.panel = `out`

		whenTransitionEnds(settings.container, () => {
			document.body.removeAttribute('data-panel')
			settings.container.classList.remove('active')

			scrollLockManager().enable(
				settings.computeScrollContainer
					? settings.computeScrollContainer()
					: settings.container.querySelector('.ct-panel-content')
			)

			focusLockManager().focusLockOff(
				settings.container.querySelector('.ct-panel-content').parentNode
			)

			clearSettings(settings)

			ctEvents.trigger('ct:modal:closed', settings.container)
		})
	}

	if (settings.container.__overlay_observer__) {
		settings.container.__overlay_observer__.disconnect()
		settings.container.__overlay_observer__ = null
	}

	window.removeEventListener('click', settings.handleWindowClick, {
		capture: true,
	})

	settings.container.removeEventListener(
		'click',
		settings.handleContainerClick
	)

	settings.onClose()
}

export const handleClick = (e, settings) => {
	if (e && e.preventDefault) {
		e.preventDefault()
	}

	settings = {
		onClose: () => {},
		container: null,
		focus: false,
		clickOutside: true,
		isModal: false,
		computeScrollContainer: null,
		closeWhenLinkInside: false,

		shouldBeInert: !!settings.container.inert,

		handleContainerClick: (event) => {
			const isPanelHeadContent = event.target.closest('.ct-panel-actions')
			let isInsidePanelContent = event.target.closest('.ct-panel-content')
			let isPanelContentItself =
				[
					...settings.container.querySelectorAll('.ct-panel-content'),
				].indexOf(event.target) > -1

			let maybeTarget = null

			if (event.target.matches('[data-toggle-panel],[href*="modal"]')) {
				maybeTarget = event.target
			}

			if (
				!maybeTarget &&
				event.target.closest('[data-toggle-panel],[href*="modal"]')
			) {
				maybeTarget = event.target.closest(
					'[data-toggle-panel],[href*="modal"]'
				)
			}

			// If target has the click listener, its likely that it will
			// trigger an overlay. We should close the panel in this case.
			if (
				maybeTarget &&
				maybeTarget.hasLazyLoadClickListener &&
				// This flow is not compatible with action buttons.
				!maybeTarget.matches('[data-button-state]')
			) {
				hideOffcanvas(settings)

				setTimeout(() => {
					maybeTarget.click()
				}, 650)
				return
			}

			if (
				(settings.isModal &&
					!isPanelContentItself &&
					isInsidePanelContent) ||
				(!settings.isModal &&
					(isPanelContentItself ||
						isInsidePanelContent ||
						isPanelHeadContent)) ||
				event.target.closest('[class*="select2-container"]') ||
				// Element was clicked upon but suddenly got removed from the DOM
				!event.target.closest('body') ||
				!event.target.closest('.ct-panel')
			) {
				return
			}

			if (window.getSelection().toString().length > 0) {
				return
			}

			document.body.hasAttribute('data-panel') && hideOffcanvas(settings)
		},
		handleWindowClick: (e) => {
			setTimeout(() => {
				if (
					settings.container.contains(e.target) ||
					e.target === document.body ||
					e.target.closest('[class*="select2-container"]') ||
					!e.target.closest('body')
				) {
					return
				}

				if (!document.body.hasAttribute('data-panel')) {
					return
				}

				hideOffcanvas(settings)
			})
		},
		...settings,
	}

	persistSettings(settings)

	showOffcanvas(settings)

	if (settings.closeWhenLinkInside) {
		if (!settings.container.hasListener) {
			settings.container.hasListener = true

			settings.container.addEventListener('click', (event) => {
				if (!event.target) {
					return
				}

				let maybeA = event.target

				if (event.target.closest('a')) {
					maybeA = event.target.closest('a')
				}

				if (!maybeA.closest('.ct-panel')) {
					return
				}

				if (!maybeA.closest('.ct-panel').classList.contains('active')) {
					return
				}

				if (!maybeA.matches('a')) {
					return
				}

				if (maybeA.classList.contains('ct-overlay-skip')) {
					return
				}

				if (
					!maybeA.closest('nav[data-id*="menu"]') &&
					!maybeA.closest('[data-id*="text"]') &&
					!maybeA.closest('[data-id*="button"]') &&
					!maybeA.matches('.ct-offcanvas-trigger') &&
					!maybeA.matches('.ct-header-account') &&
					!maybeA.closest('.widget_nav_menu')
				) {
					return
				}

				hideOffcanvas(settings, {
					closeInstant: maybeA.getAttribute('href')[0] !== '#',
					shouldFocusOriginalTrigger: false,
				})

				setTimeout(() => {
					if (
						maybeA.matches('.ct-offcanvas-trigger') ||
						maybeA.matches('.ct-header-account')
					) {
						maybeA.click()
					}
				}, 500)
			})
		}
	}
}

ctEvents.on('ct:offcanvas:force-close', (settings) => hideOffcanvas(settings))

export const mount = (el, { event, focus = false }) => {
	handleClick(event, {
		isModal: true,
		container: document.querySelector(el.dataset.togglePanel || el.hash),
		clickOutside: true,
		focus,
	})
}
