import { isAST, isAtoms } from "./types.js";
//#region node_modules/@ghostery/adblocker-extended-selectors/dist/esm/parse.js
/*!
* Based on parsel. Extended by Rémi Berson for Ghostery (2021).
* https://github.com/LeaVerou/parsel
*
* MIT License
*
* Copyright (c) 2020 Lea Verou
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
var RECURSIVE_PSEUDO_CLASSES = new Set([
	"any",
	"dir",
	"has",
	"host-context",
	"is",
	"matches",
	"not",
	"where"
]);
var TOKENS = {
	attribute: /\[\s*(?:(?<namespace>\*|[-\w]*)\|)?(?<name>[-\w\u{0080}-\u{FFFF}]+)\s*(?:(?<operator>\W?=)\s*(?<value>.+?)\s*(?<caseSensitive>[iIsS])?\s*)?\]/gu,
	id: /#(?<name>(?:[-\w\u{0080}-\u{FFFF}]|\\.)+)/gu,
	class: /\.(?<name>(?:[-\w\u{0080}-\u{FFFF}]|\\.)+)/gu,
	comma: /\s*,\s*/g,
	combinator: /\s*[\s>+~]\s*/g,
	"pseudo-element": /::(?<name>[-\w\u{0080}-\u{FFFF}]+)(?:\((?:¶*)\))?/gu,
	"pseudo-class": /:(?<name>[-\w\u{0080}-\u{FFFF}]+)(?:\((?<argument>¶*)\))?/gu,
	type: /(?:(?<namespace>\*|[-\w]*)\|)?(?<name>[-\w\u{0080}-\u{FFFF}]+)|\*/gu
};
var TOKENS_WITH_PARENS = new Set(["pseudo-class", "pseudo-element"]);
var TOKENS_WITH_STRINGS = new Set([...TOKENS_WITH_PARENS, "attribute"]);
var TRIM_TOKENS = new Set(["combinator", "comma"]);
var TOKENS_FOR_RESTORE = Object.assign({}, TOKENS);
TOKENS_FOR_RESTORE["pseudo-element"] = RegExp(TOKENS["pseudo-element"].source.replace("(?<argument>¶*)", "(?<argument>.*?)"), "gu");
TOKENS_FOR_RESTORE["pseudo-class"] = RegExp(TOKENS["pseudo-class"].source.replace("(?<argument>¶*)", "(?<argument>.*)"), "gu");
function splitOnMatch(pattern, str) {
	pattern.lastIndex = 0;
	const match = pattern.exec(str);
	if (match === null) return;
	const from = match.index - 1;
	const content = match[0];
	const before = str.slice(0, from + 1);
	const after = str.slice(from + content.length + 1);
	return [
		before,
		[content, match.groups || {}],
		after
	];
}
var GRAMMAR = [
	(str) => {
		const match = splitOnMatch(TOKENS.attribute, str);
		if (match === void 0) return;
		const [before, [content, { name, operator, value, namespace, caseSensitive }], after] = match;
		if (name === void 0) return;
		return [
			before,
			{
				type: "attribute",
				content,
				length: content.length,
				namespace,
				caseSensitive,
				pos: [],
				name,
				operator,
				value
			},
			after
		];
	},
	(str) => {
		const match = splitOnMatch(TOKENS.id, str);
		if (match === void 0) return;
		const [before, [content, { name }], after] = match;
		if (name === void 0) return;
		return [
			before,
			{
				type: "id",
				content,
				length: content.length,
				pos: [],
				name
			},
			after
		];
	},
	(str) => {
		const match = splitOnMatch(TOKENS.class, str);
		if (match === void 0) return;
		const [before, [content, { name }], after] = match;
		if (name === void 0) return;
		return [
			before,
			{
				type: "class",
				content,
				length: content.length,
				pos: [],
				name
			},
			after
		];
	},
	(str) => {
		const match = splitOnMatch(TOKENS.comma, str);
		if (match === void 0) return;
		const [before, [content], after] = match;
		return [
			before,
			{
				type: "comma",
				content,
				length: content.length,
				pos: []
			},
			after
		];
	},
	(str) => {
		const match = splitOnMatch(TOKENS.combinator, str);
		if (match === void 0) return;
		const [before, [content], after] = match;
		return [
			before,
			{
				type: "combinator",
				content,
				length: content.length,
				pos: []
			},
			after
		];
	},
	(str) => {
		const match = splitOnMatch(TOKENS["pseudo-element"], str);
		if (match === void 0) return;
		const [before, [content, { name }], after] = match;
		if (name === void 0) return;
		return [
			before,
			{
				type: "pseudo-element",
				content,
				length: content.length,
				pos: [],
				name
			},
			after
		];
	},
	(str) => {
		const match = splitOnMatch(TOKENS["pseudo-class"], str);
		if (match === void 0) return;
		const [before, [content, { name, argument }], after] = match;
		if (name === void 0) return;
		return [
			before,
			{
				type: "pseudo-class",
				content,
				length: content.length,
				pos: [],
				name,
				argument,
				subtree: void 0
			},
			after
		];
	},
	(str) => {
		const match = splitOnMatch(TOKENS.type, str);
		if (match === void 0) return;
		const [before, [content, { name, namespace }], after] = match;
		return [
			before,
			{
				type: "type",
				content,
				length: content.length,
				namespace,
				pos: [],
				name
			},
			after
		];
	}
];
function tokenizeBy(text) {
	if (!text) return [];
	const strarr = [text];
	for (const tokenizer of GRAMMAR) for (let i = 0; i < strarr.length; i++) {
		const str = strarr[i];
		if (typeof str === "string") {
			const match = tokenizer(str);
			if (match !== void 0) strarr.splice(i, 1, ...match.filter((a) => a.length !== 0));
		}
	}
	let offset = 0;
	for (const token of strarr) {
		if (typeof token !== "string") {
			token.pos = [offset, offset + token.length];
			if (TRIM_TOKENS.has(token.type)) token.content = token.content.trim() || " ";
		}
		offset += token.length;
	}
	if (isAtoms(strarr)) return strarr;
	return [];
}
function restoreNested(tokens, strings, regex, types) {
	for (const str of strings) for (const token of tokens) if (types.has(token.type) && token.pos[0] < str.start && str.start < token.pos[1]) {
		const content = token.content;
		token.content = token.content.replace(regex, str.str);
		if (token.content !== content) {
			TOKENS_FOR_RESTORE[token.type].lastIndex = 0;
			const match = TOKENS_FOR_RESTORE[token.type].exec(token.content);
			if (match !== null) Object.assign(token, match.groups);
		}
	}
}
function isEscaped(str, index) {
	let backslashes = 0;
	index -= 1;
	while (index >= 0 && str[index] === "\\") {
		backslashes += 1;
		index -= 1;
	}
	return backslashes % 2 !== 0;
}
function gobbleQuotes(text, quote, start) {
	let end = start + 1;
	while ((end = text.indexOf(quote, end)) !== -1 && isEscaped(text, end) === true) end += 1;
	if (end === -1) return;
	return text.slice(start, end + 1);
}
function gobbleParens(text, start) {
	let stack = 0;
	for (let i = start; i < text.length; i++) {
		const char = text[i];
		if (char === "(") stack += 1;
		else if (char === ")") if (stack > 0) stack -= 1;
		else return;
		if (stack === 0) return text.slice(start, i + 1);
	}
}
function replace(selector, replacement, opening, gobble) {
	const strings = [];
	let offset = 0;
	while ((offset = selector.indexOf(opening, offset)) !== -1) {
		const str = gobble(selector, offset);
		if (str === void 0) break;
		strings.push({
			str,
			start: offset
		});
		selector = `${selector.slice(0, offset + 1)}${replacement.repeat(str.length - 2)}${selector.slice(offset + str.length - 1)}`;
		offset += str.length;
	}
	return [strings, selector];
}
function tokenize(selector) {
	if (typeof selector !== "string") return [];
	selector = selector.trim();
	if (selector.length === 0) return [];
	const [doubleQuotes, selectorWithoutDoubleQuotes] = replace(selector, "§", "\"", (text, start) => gobbleQuotes(text, "\"", start));
	const [singleQuotes, selectorWithoutQuotes] = replace(selectorWithoutDoubleQuotes, "§", "'", (text, start) => gobbleQuotes(text, "'", start));
	const [parens, selectorWithoutParens] = replace(selectorWithoutQuotes, "¶", "(", gobbleParens);
	const tokens = tokenizeBy(selectorWithoutParens);
	restoreNested(tokens, parens, /\(¶*\)/, TOKENS_WITH_PARENS);
	restoreNested(tokens, doubleQuotes, /"§*"/, TOKENS_WITH_STRINGS);
	restoreNested(tokens, singleQuotes, /'§*'/, TOKENS_WITH_STRINGS);
	return tokens;
}
function nestTokens(tokens, { list = true } = {}) {
	if (list === true && tokens.some((t) => t.type === "comma")) {
		const selectors = [];
		const temp = [];
		for (let i = 0; i < tokens.length; i += 1) {
			const token = tokens[i];
			if (token.type === "comma") {
				if (temp.length === 0) throw new Error("Incorrect comma at " + i);
				const sub = nestTokens(temp, { list: false });
				if (sub !== void 0) selectors.push(sub);
				temp.length = 0;
			} else temp.push(token);
		}
		if (temp.length === 0) throw new Error("Trailing comma");
		else {
			const sub = nestTokens(temp, { list: false });
			if (sub !== void 0) selectors.push(sub);
		}
		return {
			type: "list",
			list: selectors
		};
	}
	for (let i = tokens.length - 1; i >= 0; i--) {
		const token = tokens[i];
		if (token.type === "combinator") {
			const left = nestTokens(tokens.slice(0, i));
			const right = nestTokens(tokens.slice(i + 1));
			if (right === void 0) return;
			if (token.content !== " " && token.content !== "~" && token.content !== "+" && token.content !== ">") return;
			return {
				type: "complex",
				combinator: token.content,
				left,
				right
			};
		}
	}
	if (tokens.length === 0) return;
	if (isAST(tokens)) {
		if (tokens.length === 1) return tokens[0];
		return {
			type: "compound",
			compound: [...tokens]
		};
	}
}
function walk(node, callback, o, parent) {
	if (node === void 0) return;
	if (node.type === "complex") {
		walk(node.left, callback, o, node);
		walk(node.right, callback, o, node);
	} else if (node.type === "compound") for (const n of node.compound) walk(n, callback, o, node);
	else if (node.type === "pseudo-class" && node.subtree !== void 0 && o !== void 0 && o.type === "pseudo-class" && o.subtree !== void 0) walk(node.subtree, callback, o, node);
	callback(node, parent);
}
/**
* Parse a CSS selector
* @param selector {String} The selector to parse
* @param options.recursive {Boolean} Whether to parse the arguments of pseudo-classes like :is(), :has() etc. Defaults to true.
* @param options.list {Boolean} Whether this can be a selector list (A, B, C etc). Defaults to true.
*/
function parse(selector, { recursive = true, list = true } = {}) {
	const tokens = tokenize(selector);
	if (tokens.length === 0) return;
	const ast = nestTokens(tokens, { list });
	if (recursive === true) walk(ast, (node) => {
		if (node.type === "pseudo-class" && node.argument && node.name !== void 0 && RECURSIVE_PSEUDO_CLASSES.has(node.name)) node.subtree = parse(node.argument, {
			recursive: true,
			list: true
		});
	});
	return ast;
}
//#endregion
export { RECURSIVE_PSEUDO_CLASSES, parse, tokenize };
