import { ASSERT_EXISTS, ASSERT_NEVER_REACH_HERE, addFlag, cc, insertToSet, isCharacter } from "./utils.js";
import { digitsCharCodes, whitespaceCodes, wordCharCodes } from "./character-classes.js";
var hexDigitPattern = /[0-9a-fA-F]/, decimalPattern = /[0-9]/, decimalPatternNoZero = /[1-9]/, RegExpParser = class {
	constructor() {
		this.idx = 0, this.input = "", this.groupIdx = 0;
	}
	saveState() {
		return {
			idx: this.idx,
			input: this.input,
			groupIdx: this.groupIdx
		};
	}
	restoreState(e) {
		this.idx = e.idx, this.input = e.input, this.groupIdx = e.groupIdx;
	}
	pattern(e) {
		this.idx = 0, this.input = e, this.groupIdx = 0, this.consumeChar("/");
		let u = this.disjunction();
		this.consumeChar("/");
		let f = {
			type: "Flags",
			loc: {
				begin: this.idx,
				end: e.length
			},
			global: !1,
			ignoreCase: !1,
			multiLine: !1,
			unicode: !1,
			sticky: !1
		};
		for (; this.isRegExpFlag();) switch (this.popChar()) {
			case "g":
				addFlag(f, "global");
				break;
			case "i":
				addFlag(f, "ignoreCase");
				break;
			case "m":
				addFlag(f, "multiLine");
				break;
			case "u":
				addFlag(f, "unicode");
				break;
			case "y":
				addFlag(f, "sticky");
				break;
		}
		if (this.idx !== this.input.length) throw Error("Redundant input: " + this.input.substring(this.idx));
		return {
			type: "Pattern",
			flags: f,
			value: u,
			loc: this.loc(0)
		};
	}
	disjunction() {
		let e = [], u = this.idx;
		for (e.push(this.alternative()); this.peekChar() === "|";) this.consumeChar("|"), e.push(this.alternative());
		return {
			type: "Disjunction",
			value: e,
			loc: this.loc(u)
		};
	}
	alternative() {
		let e = [], u = this.idx;
		for (; this.isTerm();) e.push(this.term());
		return {
			type: "Alternative",
			value: e,
			loc: this.loc(u)
		};
	}
	term() {
		return this.isAssertion() ? this.assertion() : this.atom();
	}
	assertion() {
		let d = this.idx;
		switch (this.popChar()) {
			case "^": return {
				type: "StartAnchor",
				loc: this.loc(d)
			};
			case "$": return {
				type: "EndAnchor",
				loc: this.loc(d)
			};
			case "\\":
				switch (this.popChar()) {
					case "b": return {
						type: "WordBoundary",
						loc: this.loc(d)
					};
					case "B": return {
						type: "NonWordBoundary",
						loc: this.loc(d)
					};
				}
				/* c8 ignore next */
				throw Error("Invalid Assertion Escape");
			case "(":
				this.consumeChar("?");
				let u;
				switch (this.popChar()) {
					case "=":
						u = "Lookahead";
						break;
					case "!":
						u = "NegativeLookahead";
						break;
					case "<":
						switch (this.popChar()) {
							case "=":
								u = "Lookbehind";
								break;
							case "!": u = "NegativeLookbehind";
						}
						break;
				}
				ASSERT_EXISTS(u);
				let f = this.disjunction();
				return this.consumeChar(")"), {
					type: u,
					value: f,
					loc: this.loc(d)
				};
		}
		// istanbul ignore next
		return ASSERT_NEVER_REACH_HERE();
	}
	quantifier(u = !1) {
		let d, f = this.idx;
		switch (this.popChar()) {
			case "*":
				d = {
					atLeast: 0,
					atMost: Infinity
				};
				break;
			case "+":
				d = {
					atLeast: 1,
					atMost: Infinity
				};
				break;
			case "?":
				d = {
					atLeast: 0,
					atMost: 1
				};
				break;
			case "{":
				let f = this.integerIncludingZero();
				switch (this.popChar()) {
					case "}":
						d = {
							atLeast: f,
							atMost: f
						};
						break;
					case ",":
						let e;
						this.isDigit() ? (e = this.integerIncludingZero(), d = {
							atLeast: f,
							atMost: e
						}) : d = {
							atLeast: f,
							atMost: Infinity
						}, this.consumeChar("}");
						break;
				}
				if (u === !0 && d === void 0) return;
				ASSERT_EXISTS(d);
				break;
		}
		if (!(u === !0 && d === void 0) && ASSERT_EXISTS(d)) return this.peekChar(0) === "?" ? (this.consumeChar("?"), d.greedy = !1) : d.greedy = !0, d.type = "Quantifier", d.loc = this.loc(f), d;
	}
	atom() {
		let d, f = this.idx;
		switch (this.peekChar()) {
			case ".":
				d = this.dotAll();
				break;
			case "\\":
				d = this.atomEscape();
				break;
			case "[":
				d = this.characterClass();
				break;
			case "(":
				d = this.group();
				break;
		}
		// istanbul ignore next
		return d === void 0 && this.isPatternCharacter() && (d = this.patternCharacter()), ASSERT_EXISTS(d) ? (d.loc = this.loc(f), this.isQuantifier() && (d.quantifier = this.quantifier()), d) : ASSERT_NEVER_REACH_HERE();
	}
	dotAll() {
		return this.consumeChar("."), {
			type: "Set",
			complement: !0,
			value: [
				cc("\n"),
				cc("\r"),
				cc("\u2028"),
				cc("\u2029")
			]
		};
	}
	atomEscape() {
		switch (this.consumeChar("\\"), this.peekChar()) {
			case "1":
			case "2":
			case "3":
			case "4":
			case "5":
			case "6":
			case "7":
			case "8":
			case "9": return this.decimalEscapeAtom();
			case "d":
			case "D":
			case "s":
			case "S":
			case "w":
			case "W": return this.characterClassEscape();
			case "f":
			case "n":
			case "r":
			case "t":
			case "v": return this.controlEscapeAtom();
			case "c": return this.controlLetterEscapeAtom();
			case "0": return this.nulCharacterAtom();
			case "x": return this.hexEscapeSequenceAtom();
			case "u": return this.regExpUnicodeEscapeSequenceAtom();
			default: return this.identityEscapeAtom();
		}
	}
	decimalEscapeAtom() {
		return {
			type: "GroupBackReference",
			value: this.positiveInteger()
		};
	}
	characterClassEscape() {
		let d, f = !1;
		switch (this.popChar()) {
			case "d":
				d = digitsCharCodes;
				break;
			case "D":
				d = digitsCharCodes, f = !0;
				break;
			case "s":
				d = whitespaceCodes;
				break;
			case "S":
				d = whitespaceCodes, f = !0;
				break;
			case "w":
				d = wordCharCodes;
				break;
			case "W":
				d = wordCharCodes, f = !0;
				break;
		}
		// istanbul ignore next
		return ASSERT_EXISTS(d) ? {
			type: "Set",
			value: d,
			complement: f
		} : ASSERT_NEVER_REACH_HERE();
	}
	controlEscapeAtom() {
		let d;
		switch (this.popChar()) {
			case "f":
				d = cc("\f");
				break;
			case "n":
				d = cc("\n");
				break;
			case "r":
				d = cc("\r");
				break;
			case "t":
				d = cc("	");
				break;
			case "v":
				d = cc("\v");
				break;
		}
		// istanbul ignore next
		return ASSERT_EXISTS(d) ? {
			type: "Character",
			value: d
		} : ASSERT_NEVER_REACH_HERE();
	}
	controlLetterEscapeAtom() {
		this.consumeChar("c");
		let e = this.popChar();
		if (/[a-zA-Z]/.test(e) === !1) throw Error("Invalid ");
		return {
			type: "Character",
			value: e.toUpperCase().charCodeAt(0) - 64
		};
	}
	nulCharacterAtom() {
		return this.consumeChar("0"), {
			type: "Character",
			value: cc("\0")
		};
	}
	hexEscapeSequenceAtom() {
		return this.consumeChar("x"), this.parseHexDigits(2);
	}
	regExpUnicodeEscapeSequenceAtom() {
		return this.consumeChar("u"), this.parseHexDigits(4);
	}
	identityEscapeAtom() {
		return {
			type: "Character",
			value: cc(this.popChar())
		};
	}
	classPatternCharacterAtom() {
		switch (this.peekChar()) {
			case "\n":
			case "\r":
			case "\u2028":
			case "\u2029":
			case "\\":
			case "]": throw Error("TBD");
			default: return {
				type: "Character",
				value: cc(this.popChar())
			};
		}
	}
	characterClass() {
		let e = [], u = !1;
		for (this.consumeChar("["), this.peekChar(0) === "^" && (this.consumeChar("^"), u = !0); this.isClassAtom();) {
			let u = this.classAtom();
			if (u.type, isCharacter(u) && this.isRangeDash()) {
				this.consumeChar("-");
				let d = this.classAtom();
				if (d.type, isCharacter(d)) {
					if (d.value < u.value) throw Error("Range out of order in character class");
					e.push({
						from: u.value,
						to: d.value
					});
				} else insertToSet(u.value, e), e.push(cc("-")), insertToSet(d.value, e);
			} else insertToSet(u.value, e);
		}
		return this.consumeChar("]"), {
			type: "Set",
			complement: u,
			value: e
		};
	}
	classAtom() {
		switch (this.peekChar()) {
			case "]":
			case "\n":
			case "\r":
			case "\u2028":
			case "\u2029": throw Error("TBD");
			case "\\": return this.classEscape();
			default: return this.classPatternCharacterAtom();
		}
	}
	classEscape() {
		switch (this.consumeChar("\\"), this.peekChar()) {
			case "b": return this.consumeChar("b"), {
				type: "Character",
				value: cc("\b")
			};
			case "d":
			case "D":
			case "s":
			case "S":
			case "w":
			case "W": return this.characterClassEscape();
			case "f":
			case "n":
			case "r":
			case "t":
			case "v": return this.controlEscapeAtom();
			case "c": return this.controlLetterEscapeAtom();
			case "0": return this.nulCharacterAtom();
			case "x": return this.hexEscapeSequenceAtom();
			case "u": return this.regExpUnicodeEscapeSequenceAtom();
			default: return this.identityEscapeAtom();
		}
	}
	group() {
		let e = !0;
		switch (this.consumeChar("("), this.peekChar(0)) {
			case "?":
				this.consumeChar("?"), this.consumeChar(":"), e = !1;
				break;
			default:
				this.groupIdx++;
				break;
		}
		let u = this.disjunction();
		this.consumeChar(")");
		let d = {
			type: "Group",
			capturing: e,
			value: u
		};
		return e && (d.idx = this.groupIdx), d;
	}
	positiveInteger() {
		let e = this.popChar();
		// istanbul ignore next - can't ever get here due to previous lookahead checks
		if (decimalPatternNoZero.test(e) === !1) throw Error("Expecting a positive integer");
		for (; decimalPattern.test(this.peekChar(0));) e += this.popChar();
		return parseInt(e, 10);
	}
	integerIncludingZero() {
		let e = this.popChar();
		if (decimalPattern.test(e) === !1) throw Error("Expecting an integer");
		for (; decimalPattern.test(this.peekChar(0));) e += this.popChar();
		return parseInt(e, 10);
	}
	patternCharacter() {
		let e = this.popChar();
		switch (e) {
			case "\n":
			case "\r":
			case "\u2028":
			case "\u2029":
			case "^":
			case "$":
			case "\\":
			case ".":
			case "*":
			case "+":
			case "?":
			case "(":
			case ")":
			case "[":
			case "|":
 // istanbul ignore next
			throw Error("TBD");
			default: return {
				type: "Character",
				value: cc(e)
			};
		}
	}
	isRegExpFlag() {
		switch (this.peekChar(0)) {
			case "g":
			case "i":
			case "m":
			case "u":
			case "y": return !0;
			default: return !1;
		}
	}
	isRangeDash() {
		return this.peekChar() === "-" && this.isClassAtom(1);
	}
	isDigit() {
		return decimalPattern.test(this.peekChar(0));
	}
	isClassAtom(e = 0) {
		switch (this.peekChar(e)) {
			case "]":
			case "\n":
			case "\r":
			case "\u2028":
			case "\u2029": return !1;
			default: return !0;
		}
	}
	isTerm() {
		return this.isAtom() || this.isAssertion();
	}
	isAtom() {
		if (this.isPatternCharacter()) return !0;
		switch (this.peekChar(0)) {
			case ".":
			case "\\":
			case "[":
			case "(": return !0;
			default: return !1;
		}
	}
	isAssertion() {
		switch (this.peekChar(0)) {
			case "^":
			case "$": return !0;
			case "\\": switch (this.peekChar(1)) {
				case "b":
				case "B": return !0;
				default: return !1;
			}
			case "(": return this.peekChar(1) === "?" && (this.peekChar(2) === "=" || this.peekChar(2) === "!" || this.peekChar(2) === "<" && (this.peekChar(3) === "=" || this.peekChar(3) === "!"));
			default: return !1;
		}
	}
	isQuantifier() {
		let e = this.saveState();
		try {
			return this.quantifier(!0) !== void 0;
		} catch {
			return !1;
		} finally {
			this.restoreState(e);
		}
	}
	isPatternCharacter() {
		switch (this.peekChar()) {
			case "^":
			case "$":
			case "\\":
			case ".":
			case "*":
			case "+":
			case "?":
			case "(":
			case ")":
			case "[":
			case "|":
			case "/":
			case "\n":
			case "\r":
			case "\u2028":
			case "\u2029": return !1;
			default: return !0;
		}
	}
	parseHexDigits(e) {
		let u = "";
		for (let d = 0; d < e; d++) {
			let e = this.popChar();
			if (hexDigitPattern.test(e) === !1) throw Error("Expecting a HexDecimal digits");
			u += e;
		}
		return {
			type: "Character",
			value: parseInt(u, 16)
		};
	}
	peekChar(e = 0) {
		return this.input[this.idx + e];
	}
	popChar() {
		let e = this.peekChar(0);
		return this.consumeChar(void 0), e;
	}
	consumeChar(e) {
		if (e !== void 0 && this.input[this.idx] !== e) throw Error("Expected: '" + e + "' but found: '" + this.input[this.idx] + "' at offset: " + this.idx);
		if (this.idx >= this.input.length) throw Error("Unexpected end of input");
		this.idx++;
	}
	loc(e) {
		return {
			begin: e,
			end: this.idx
		};
	}
};
export { RegExpParser };
