import * as dom from '../../commons/dom';
import * as aria from '../../commons/aria';
import * as standards from '../../commons/standards';
import matches from '../../commons/matches';
import cache from '../../core/base/cache';

const landmarkRoles = standards.getAriaRolesByType('landmark');
const implicitAriaLiveRoles = ['alert', 'log', 'status'];

// Check if the current element is a landmark
function isRegion(virtualNode, options) {
	const node = virtualNode.actualNode;
	const role = aria.getRole(virtualNode);
	const ariaLive = (node.getAttribute('aria-live') || '').toLowerCase().trim();

	// Ignore content inside of aria-live
	if (
		['assertive', 'polite'].includes(ariaLive) ||
		implicitAriaLiveRoles.includes(role)
	) {
		return true;
	}

	// Check if the node matches a landmark role
	if (landmarkRoles.includes(role)) {
		return true;
	}

	// Check if node matches an option
	if (options.regionMatcher && matches(virtualNode, options.regionMatcher)) {
		return true;
	}

	return false;
}

/**
 * Find all visible elements not wrapped inside a landmark or skiplink
 */
function findRegionlessElms(virtualNode, options) {
	const node = virtualNode.actualNode;
	// End recursion if the element is a landmark, skiplink, or hidden content
	if (
		isRegion(virtualNode, options) ||
		(dom.isSkipLink(virtualNode.actualNode) &&
			dom.getElementByReference(virtualNode.actualNode, 'href')) ||
		!dom.isVisible(node, true)
	) {
		// Mark each parent node as having region descendant
		let vNode = virtualNode;
		while (vNode) {
			vNode._hasRegionDescendant = true;
			vNode = vNode.parent;
		}

		return [];

		// Return the node is a content element. Ignore any direct text children
		// of body so we don't report body as being outside of a landmark.
		// @see https://github.com/dequelabs/axe-core/issues/2049
	} else if (
		node !== document.body &&
		dom.hasContent(node, /* noRecursion: */ true)
	) {
		return [virtualNode];

		// Recursively look at all child elements
	} else {
		return virtualNode.children
			.filter(({ actualNode }) => actualNode.nodeType === 1)
			.map(vNode => findRegionlessElms(vNode, options))
			.reduce((a, b) => a.concat(b), []); // flatten the results
	}
}

function regionEvaluate(node, options, virtualNode) {
	let regionlessNodes = cache.get('regionlessNodes');
	if (regionlessNodes) {
		return !regionlessNodes.includes(virtualNode);
	}

	const tree = axe._tree;
	regionlessNodes = findRegionlessElms(tree[0], options)
		// Find first parent marked as having region descendant (or body) and
		// return the node right before it as the "outer" element
		.map(vNode => {
			while (
				vNode.parent &&
				!vNode.parent._hasRegionDescendant &&
				vNode.parent.actualNode !== document.body
			) {
				vNode = vNode.parent;
			}

			return vNode;
		})
		// Remove duplicate containers
		.filter((vNode, index, array) => {
			return array.indexOf(vNode) === index;
		});
	cache.set('regionlessNodes', regionlessNodes);

	return !regionlessNodes.includes(virtualNode);
}

export default regionEvaluate;
