/* global CleanViewToast, CleanViewModal, puredashConfig, jQuery, confirm */
/**
 * CleanView Engine
 * Handles the selection and blocking logic.
 *
 * @param {Window}   window   - The global window object.
 * @param {Document} document - The document object.
 * @param {Object}   $        - jQuery object.
 */
(function (window, document, $) {
	'use strict';

	const Engine = {
		isActive: false,
		hoverOverlay: null,
		currentTarget: null,
		config: window.puredashConfig || {},

		/**
		 * WP Core Canonical Mapping
		 * Ensures stable selectors for standard WordPress elements.
		 */
		CORE_MAPPING: {
			'#adminmenu': 'menu',
			'#wpadminbar': 'admin-bar',
			'#dashboard_right_now': 'dashboard',
			'#dashboard_activity': 'dashboard',
			'#dashboard_quick_press': 'dashboard',
			'#dashboard_primary': 'dashboard',
			'#dashboard_secondary': 'dashboard',
			'.postbox': 'dashboard',
			'.notice': 'notices',
			'.updated': 'notices',
			'.error': 'notices',
			'.notice-info': 'notices',
			'.notice-warning': 'notices',
			'.notice-success': 'notices',
			'.notice-error': 'notices',
		},

		init() {
			if (!this.config.nonce) {
				return;
			}

			this.createOverlay();
			this.bindEvents();
		},

		createOverlay() {
			this.hoverOverlay = document.createElement('div');
			this.hoverOverlay.className = 'cleanview-hover-overlay';
			document.body.appendChild(this.hoverOverlay);
		},

		bindEvents() {
			// Admin Bar Toggle
			const toggleBtn = document.getElementById(
				'wp-admin-bar-cleanview-toggle'
			);
			if (toggleBtn) {
				toggleBtn.addEventListener('click', (e) => {
					e.preventDefault();
					this.toggleMode();
				});
			}

			// Admin Bar: Reset All
			const resetAllBtn = document.getElementById(
				'wp-admin-bar-cleanview-reset-all'
			);
			if (resetAllBtn) {
				resetAllBtn
					.querySelector('a')
					.addEventListener('click', (e) => {
						// eslint-disable-next-line no-alert
						if (
							// eslint-disable-next-line no-alert
							!confirm(
								'Warning: This will delete ALL blocked elements from the database.\n\nAre you sure completely reset PureDash Cleaner?'
							)
						) {
							e.preventDefault();
						}
					});
			}

			// Document Hover
			document.addEventListener('mouseover', (e) => {
				if (!this.isActive) {
					return;
				}
				this.handleHover(e);
			}, true);

			// Document Click
			document.addEventListener('click', (e) => {
				if (!this.isActive) {
					return;
				}

				// Allow clicking the toggle button itself
				if (
					e.target.closest('#wp-admin-bar-cleanview-toggle') ||
					e.target.closest('.cleanview-modal-overlay')
				) {
					return;
				}

				e.preventDefault();
				e.stopPropagation();
				this.handleClick(e);
			}, true);

			// Escape to exit mode
			document.addEventListener('keydown', (e) => {
				if (e.key === 'Escape' && this.isActive) {
					this.toggleMode(false);
				}
			});
		},

		toggleMode(forceState) {
			this.isActive = typeof forceState !== 'undefined' ? forceState : !this.isActive;

			if (this.isActive) {
				document.body.classList.add('cleanview-mode-active');
			} else {
				document.body.classList.remove('cleanview-mode-active');
				this.hideOverlay();
			}
		},

		handleHover(e) {
			const target = e.target;

			// Ignore our tools, overlay, and modal
			if (target.closest('#wp-admin-bar-cleanview-parent') ||
				target.classList.contains('cleanview-hover-overlay') ||
				target.closest('.cleanview-modal-overlay')) {
				this.hideOverlay();
				return;
			}

			this.currentTarget = target;
			this.positionOverlay(target);
		},

		positionOverlay(target) {
			const rect = target.getBoundingClientRect();
			const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
			const scrollTop = window.pageYOffset || document.documentElement.scrollTop;

			this.hoverOverlay.style.width = rect.width + 'px';
			this.hoverOverlay.style.height = rect.height + 'px';
			this.hoverOverlay.style.top = (rect.top + scrollTop) + 'px';
			this.hoverOverlay.style.left = (rect.left + scrollLeft) + 'px';
			this.hoverOverlay.style.display = 'block';
		},

		hideOverlay() {
			if (this.hoverOverlay) {
				this.hoverOverlay.style.display = 'none';
			}
		},

		handleClick() {
			if (!this.currentTarget) {
				return;
			}

			try {
				const selector = this.generateSelector(this.currentTarget);

				// 1. Conflict Prevention: Specificity Check
				const count = document.querySelectorAll(selector).length;
				let showWarning = false;
				let warningMsg = '';

				if (count > 1) {
					showWarning = true;
					warningMsg = `Attention: This selector matches ${count} elements. Do you want to block all of them?`;
				}

				// 2. Redundancy Check: Is it already inside a blocked element?
				// This is hard to check perfectly without list of all blocked selectors locally, 
				// but we can check if it matches the 'display: none' style if injected, 
				// but since we are in "Zen Mode", styles might not be applied or we might be looking at raw DOM.
				// We'll skip complex redundancy check for now as it requires syncing blacklist to JS.

				// Whitelist Check
				if (this.isWhitelisted(selector, this.currentTarget)) {
					CleanViewToast.warning('Safety Warning: This element is critical and cannot be blocked.');
					return;
				}

				const category = this.detectCategory(this.currentTarget, selector);

				CleanViewModal.open({
					title: this.config.i18n.confirmTitle,
					selector: selector,
					category: category,
					label: this.getClassLabel(this.currentTarget),
					element: this.currentTarget, // Pass element for Preview mode
					showWarning: showWarning,
					warning: warningMsg,
					i18n: this.config.i18n,
					onConfirm: (data) => {
						this.saveBlock(selector, data);
					},
					onCancel: () => { }
				});
			} catch (err) {
				// eslint-disable-next-line no-console
				console.error('CleanView Selection Error:', err);
				CleanViewToast.error('Error generating selector. Check console.');
			}
		},

		detectCategory(el, selector) {
			// Check against CORE_MAPPING
			for (const [mapSelector, category] of Object.entries(this.CORE_MAPPING)) {
				if (el.closest(mapSelector)) {
					return category;
				}
			}

			// Heuristic based on selector string if not matched by DOM traversal
			if (selector.includes('#adminmenu')) return 'menu';
			if (selector.includes('#wpadminbar') || selector.includes('#wp-admin-bar-')) return 'admin-bar';
			if (selector.includes('.postbox') || selector.includes('#dashboard')) return 'dashboard';
			if (selector.includes('.notice') || selector.includes('.updated') || selector.includes('.error')) return 'notices';

			return 'other';
		},

		generateSelector(el) {
			if (el.tagName.toLowerCase() === 'html') return 'html';
			if (el.tagName.toLowerCase() === 'body') return 'body';

			// 0. WP Core Canonical Interception
			for (const [selector, category] of Object.entries(this.CORE_MAPPING)) {
				const canonicalEl = el.closest(selector);
				if (canonicalEl) {
					// Specific mapping for Menu/Submenu to avoid hiding the whole sidebar
					if (selector === '#adminmenu' && el.closest('li')) {
						// Let the specific Menu strategy below handle it
						break;
					}
					// Specific mapping for Admin Bar to avoid hiding the whole bar
					if (selector === '#wpadminbar' && el.closest('li')) {
						// Target the specific node ID if present
						const node = el.closest('li[id^="wp-admin-bar-"]');
						if (node) return '#' + node.id;
						break;
					}
					
					// For other canonicals like Dashboard widgets, use the ID if present
					if (canonicalEl.id && ! /(\d{3,}|[-_]\d+)/.test(canonicalEl.id)) {
						return '#' + canonicalEl.id;
					}
					
					// Return the selector itself if it's a class (like .notice)
					if (selector.startsWith('.')) {
						// Only return if it's the element itself or a very close container
						if (el === canonicalEl || canonicalEl.contains(el)) {
							// For notices, we often want the specific class combination
							if (selector === '.notice') {
								const classes = Array.from(canonicalEl.classList).filter(c => c.startsWith('notice-') || c === 'notice');
								if (classes.length > 0) return '.' + classes.join('.');
							}
							return selector;
						}
					}
				}
			}

			// 0.5 Admin Menu Strategy: Surgical targeting to avoid hiding parent menus
			const adminMenuLi = el.closest('#adminmenu li');
			if (adminMenuLi) {
				// Check if we're inside a submenu (.wp-submenu)
				const isInSubmenu = el.closest('.wp-submenu') !== null;

				if (isInSubmenu) {
					// SUBMENU ITEM: Target the specific <li> parent via partial href match.
					const link = el.closest('a');
					if (link) {
						const hrefAttr = link.getAttribute('href');
						if (hrefAttr && hrefAttr !== '#' && hrefAttr.length > 3) {
							// Strip nonce more safely using regex
							const cleanHref = hrefAttr.replace(/([?&])_wpnonce=[^&]+/, '').replace(/[?&]$/, '');
							// Hide the <li> container using :has() for perfect visual cleanup
							// Use single quotes to avoid double-escaping issues
							return `#adminmenu .wp-submenu li:has(a[href*='${cleanHref.replace(/'/g, "\\'")}'])`;
						}
					}
					// If no specific link found in submenu (background click), don't return a generic LI selector
					// Fall through to class-based or structural strategy for the specific element
				} else {
					// For menus WITHOUT submenus, safe to target the li directly
					if (adminMenuLi.id && ! /(\d{3,}|[-_]\d+)/.test(adminMenuLi.id)) {
						const safeId = window.CSS && window.CSS.escape ? window.CSS.escape(adminMenuLi.id) : adminMenuLi.id;
						return '#' + safeId;
					}
					// Fallback to class combination for top-level
					if (adminMenuLi.className && typeof adminMenuLi.className === 'string') {
						const stateClasses = ['current', 'wp-has-current-submenu', 'wp-has-submenu', 'wp-menu-open', 'wp-not-current-submenu', 'menu-top-first', 'menu-top-last'];
						const classes = adminMenuLi.className.split(/\s+/).filter((c) => {
							return c.length > 2 && !c.startsWith('cleanview-') && ! /^\d+$/.test(c) && !stateClasses.includes(c);
						});
						if (classes.length > 0) {
							const safeClasses = classes.map((c) => window.CSS && window.CSS.escape ? window.CSS.escape(c) : c);
							return '#adminmenu li.' + safeClasses.join('.');
						}
					}
				}
			}

			// 1. ID Strategy (Strict Filter)
			// Reject generated IDs like 'el-1234', 'ui-id-5'
			if (el.id && ! /(\d{3,}|[-_]\d+)/.test(el.id)) {
				const safeId = window.CSS && window.CSS.escape ? window.CSS.escape(el.id) : el.id;
				return '#' + safeId;
			}

			// 2. HREF Surgical Strategy (for links or inside links)
			const link = el.closest('a');
			if (link && link.href) {
				const url = new URL(link.href);
				const params = new URLSearchParams(url.search);

				// WordPress Admin typical params: 'page', 'action', 'tab'
				if (params.has('page')) {
					return `a[href*="page=${params.get('page')}"]`;
				}
				if (params.has('action')) {
					return `a[href*="action=${params.get('action')}"]`;
				}
				// Fallback to minimal href match if specific
				// Avoid matching '#' or 'admin.php' generic
				const hrefAttr = link.getAttribute('href');
				if (hrefAttr && hrefAttr !== '#' && hrefAttr.length > 5) {
					return `a[href="${hrefAttr.replace(/"/g, '\\"')}"]`;
				}
			}

			// 3. Class Combinations
			if (el.className && typeof el.className === 'string') {
				const stateClasses = ['active', 'current', 'open', 'hover', 'focus', 'selected', 'current-menu-item', 'current-menu-parent', 'current-menu-ancestor'];
				const classes = el.className.split(/\s+/).filter((c) => {
					return !c.startsWith('cleanview-') && !c.startsWith('ng-') && c.length > 2 && ! /^\d+$/.test(c) && !stateClasses.includes(c);
				});

				if (classes.length > 0) {
					// If we have a specific class (not generic like 'wrap', 'notice'), use it.
					// Heuristic: generic classes usually simple words. Specific often dashed.
					// For now, join all non-blacklisted classes.
					const safeClasses = classes.map((c) => window.CSS && window.CSS.escape ? window.CSS.escape(c) : c);

					// If only 1 class and it looks generic (no dashes, < 6 chars), try to add parent
					if (classes.length === 1 && classes[0].indexOf('-') === -1 && classes[0].length < 6) {
						// Fallthrough to structural or parent combination
					} else {
						return '.' + safeClasses.join('.');
					}
				}
			}

			// 4. Attribute Matching (src, name, data-*)
			const attrs = ['name', 'data-id', 'data-slug', 'src'];
			for (let attr of attrs) {
				if (el.hasAttribute(attr)) {
					const val = el.getAttribute(attr);
					if (val && val.length > 2) {
						return `${el.tagName.toLowerCase()}[${attr}="${val.replace(/"/g, '\\"')}"]`;
					}
				}
			}

			// 5. Structural Fallback (nth-of-type) - The "Last Resort"
			// We walk up to find a stable parent, then use path
			let path = [];
			let current = el;
			let depth = 0;
			const maxDepth = 5; // Don't go too deep

			while (current && current.nodeType === Node.ELEMENT_NODE && current.tagName.toLowerCase() !== 'html' && depth < maxDepth) {
				let nodeSelector = current.tagName.toLowerCase();

				if (current.id && ! /(\d{3,}|[-_]\d+)/.test(current.id)) {
					nodeSelector = '#' + (window.CSS && window.CSS.escape ? window.CSS.escape(current.id) : current.id);
					path.unshift(nodeSelector);
					break; // Found anchor
				} else if (current.className) {
					const classes = current.className.split(/\s+/).filter((c) => !c.startsWith('cleanview-') && c.length > 2);
					if (classes.length > 0) {
						nodeSelector += '.' + classes.map((c) => window.CSS && window.CSS.escape ? window.CSS.escape(c) : c).join('.');
					}
				}

				if (current !== el) { // Only use nth-of-type for the target or generic parents
					// actually, let's keep it simple: just tag+class
				}

				// Add nth-of-type if needed for uniqueness among siblings
				const parent = current.parentNode;
				if (parent && parent.children) {
					const siblings = Array.from(parent.children).filter((c) => c.tagName === current.tagName);
					if (siblings.length > 1) {
						const index = siblings.indexOf(current) + 1;
						nodeSelector += `:nth-of-type(${index})`;
					}
				}

				path.unshift(nodeSelector);
				current = current.parentNode;
				depth++;
			}

			return path.join(' > ');
		},

		getClassLabel(el) {
			// Try to find text content, or class name
			let text = el.innerText || el.textContent;
			if (text && text.length < 30) return text.trim();
			if (el.id) return '#' + el.id;
			if (el.className) return '.' + el.className.split(' ')[0];
			return el.tagName.toLowerCase();
		},

		isWhitelisted(selector, el) {
			// Client-side quick check
			if (!this.config.whitelist) return false;

			// 1. Explicitly protect PureDash Cleaner's own UI and its children
			if (el && (el.closest('.cleanview-modal-overlay') || el.closest('.cleanview-toast-container') || el.closest('#wp-admin-bar-cleanview-parent'))) {
				return true;
			}

			// 2. Check the general whitelist (only matches, not closest, to allow hiding children of containers)
			if (el) {
				try {
					const isProtected = this.config.whitelist.some((w) => el.matches(w));
					if (isProtected) return true;
				} catch (error) {
					// eslint-disable-next-line no-console
					console.error('Invalid whitelist selector check', error);
				}
			}

			// Fallback to strict equality if no element provided
			return this.config.whitelist.some((w) => selector === w);
		},

		saveBlock(selector, data) {
			// Immediate visual feedback
			this.hideElement(selector);
			this.toggleMode(false);

			if (data.isSession) {
				// For temporary hide (Reload to reset), we just apply the visual hide
				// without saving to sessionStorage or database.
				CleanViewToast.success(this.config.i18n.sessionOnly + '! Reload the page to restore it.');
				return;
			}

			$.post(this.config.ajaxUrl, {
				action: 'puredash_save_block',
				security: this.config.nonce,
				selector: selector,
				label: data.label,
				category: data.category || 'other'
			}, (response) => {
				if (response.success) {
					CleanViewToast.success('Element blocked successfully! Reloading...');

					// Reload to confirm/clean up
					setTimeout(() => {
						window.location.reload();
					}, 1500);
				} else {
					// Restore visibility if failed
					CleanViewToast.error('Error: ' + response.data.message);
					// Optional: remove the temporary style tag here if we had a way to target it
				}
			}).fail((err) => {
				CleanViewToast.error('Request failed. Check console.');
				// eslint-disable-next-line no-console
				console.error(err);
			});
		},

		hideElement(selector) {
			const style = document.createElement('style');
			style.textContent = selector + ' { display: none !important; opacity: 0 !important; pointer-events: none !important; }';
			document.head.appendChild(style);
		}
	};

	// Initialize on load
	$(document).ready(function () {
		Engine.init();
	});

	// Expose for testing
	if (typeof window !== 'undefined') {
		window.CleanViewEngine = Engine;
	}

})(window, document, jQuery);
