import { stream } from "../utils/stream.js";
import { isAbstractParserRule, isAction, isAlternatives, isAssignment, isBooleanLiteral, isConjunction, isCrossReference, isDisjunction, isEndOfFile, isGroup, isInfixRule, isKeyword, isNegation, isParameterReference, isParserRule, isRuleCall, isTerminalRule, isUnorderedGroup } from "../languages/generated/ast.js";
import { ErrorWithLocation, assertUnreachable } from "../utils/errors.js";
import { findNameAssignment, getAllReachableRules, getTypeName } from "../utils/grammar-utils.js";
import { EOF } from "../../../chevrotain/lib/src/scan/tokens_public.js";
import { EMPTY_ALT } from "../../../chevrotain/lib/src/parse/parser/parser.js";
import "../../../chevrotain/lib/src/api.js";
function createParser(d, P, F) {
	return buildRules({
		parser: P,
		tokens: F,
		ruleNames: /* @__PURE__ */ new Map()
	}, d), P;
}
function buildRules(P, F) {
	let I = getAllReachableRules(F, !1), L = stream(F.rules).filter(isParserRule).filter((d) => I.has(d));
	for (let d of L) {
		let F = {
			...P,
			consume: 1,
			optional: 1,
			subrule: 1,
			many: 1,
			or: 1
		};
		P.parser.rule(d, buildElement(F, d.definition));
	}
	let R = stream(F.rules).filter(isInfixRule).filter((d) => I.has(d));
	for (let d of R) P.parser.rule(d, buildInfixRule(P, d));
}
function buildInfixRule(d, P) {
	let F = P.call.rule.ref;
	if (!F) throw Error("Could not resolve reference to infix operator rule: " + P.call.rule.$refText);
	if (isTerminalRule(F)) throw Error("Cannot use terminal rule in infix expression");
	let I = P.operators.precedences.flatMap((d) => d.operators), L = {
		$type: "Group",
		elements: []
	}, R = {
		$container: L,
		$type: "Assignment",
		feature: "parts",
		operator: "+=",
		terminal: P.call
	}, z = {
		$container: L,
		$type: "Group",
		elements: [],
		cardinality: "*"
	};
	L.elements.push(R, z);
	let B = {
		$container: z,
		$type: "Assignment",
		feature: "operators",
		operator: "+=",
		terminal: {
			$type: "Alternatives",
			elements: I
		}
	}, V = {
		...R,
		$container: z
	};
	z.elements.push(B, V);
	let H = I.map((P) => d.tokens[P.value]).map((P, F) => ({ ALT: () => d.parser.consume(F, P, B) })), U;
	return (P) => {
		U ??= getRule(d, F), d.parser.subrule(0, U, !1, R, P), d.parser.many(0, { DEF: () => {
			d.parser.alternatives(0, H), d.parser.subrule(1, U, !1, V, P);
		} });
	};
}
function buildElement(d, P, R = !1) {
	let z;
	if (isKeyword(P)) z = buildKeyword(d, P);
	else if (isAction(P)) z = buildAction(d, P);
	else if (isAssignment(P)) z = buildElement(d, P.terminal);
	else if (isCrossReference(P)) z = buildCrossReference(d, P);
	else if (isRuleCall(P)) z = buildRuleCall(d, P);
	else if (isAlternatives(P)) z = buildAlternatives(d, P);
	else if (isUnorderedGroup(P)) z = buildUnorderedGroup(d, P);
	else if (isGroup(P)) z = buildGroup(d, P);
	else if (isEndOfFile(P)) {
		let F = d.consume++;
		z = () => d.parser.consume(F, EOF, P);
	} else throw new ErrorWithLocation(P.$cstNode, `Unexpected element type: ${P.$type}`);
	return wrap(d, R ? void 0 : getGuardCondition(P), z, P.cardinality);
}
function buildAction(d, P) {
	let F = getTypeName(P);
	return () => d.parser.action(F, P);
}
function buildRuleCall(d, F) {
	let I = F.rule.ref;
	if (isAbstractParserRule(I)) {
		let P = d.subrule++, L = isParserRule(I) && I.fragment, R = F.arguments.length > 0 ? buildRuleCallPredicate(I, F.arguments) : () => ({}), z;
		return (B) => {
			z ??= getRule(d, I), d.parser.subrule(P, z, L, F, R(B));
		};
	} else if (isTerminalRule(I)) {
		let P = d.consume++, L = getToken(d, I.name);
		return () => d.parser.consume(P, L, F);
	} else if (I) assertUnreachable(I);
	else throw new ErrorWithLocation(F.$cstNode, `Undefined rule: ${F.rule.$refText}`);
}
function buildRuleCallPredicate(d, P) {
	if (P.some((d) => d.calledByName)) {
		let d = P.map((d) => ({
			parameterName: d.parameter?.ref?.name,
			predicate: buildPredicate(d.value)
		}));
		return (P) => {
			let F = {};
			for (let { parameterName: I, predicate: L } of d) I && (F[I] = L(P));
			return F;
		};
	} else {
		let F = P.map((d) => buildPredicate(d.value));
		return (P) => {
			let I = {};
			for (let L = 0; L < F.length; L++) if (L < d.parameters.length) {
				let R = d.parameters[L].name, z = F[L];
				I[R] = z(P);
			}
			return I;
		};
	}
}
function buildPredicate(d) {
	if (isDisjunction(d)) {
		let P = buildPredicate(d.left), F = buildPredicate(d.right);
		return (d) => P(d) || F(d);
	} else if (isConjunction(d)) {
		let P = buildPredicate(d.left), F = buildPredicate(d.right);
		return (d) => P(d) && F(d);
	} else if (isNegation(d)) {
		let P = buildPredicate(d.value);
		return (d) => !P(d);
	} else if (isParameterReference(d)) {
		let P = d.parameter.ref.name;
		return (d) => d !== void 0 && d[P] === !0;
	} else if (isBooleanLiteral(d)) {
		let P = !!d.true;
		return () => P;
	}
	assertUnreachable(d);
}
function buildAlternatives(d, P) {
	if (P.elements.length === 1) return buildElement(d, P.elements[0]);
	{
		let F = [];
		for (let I of P.elements) {
			let P = { ALT: buildElement(d, I, !0) }, L = getGuardCondition(I);
			L && (P.GATE = buildPredicate(L)), F.push(P);
		}
		let I = d.or++;
		return (P) => d.parser.alternatives(I, F.map((d) => {
			let F = { ALT: () => d.ALT(P) }, I = d.GATE;
			return I && (F.GATE = () => I(P)), F;
		}));
	}
}
function buildUnorderedGroup(d, P) {
	if (P.elements.length === 1) return buildElement(d, P.elements[0]);
	let F = [];
	for (let I of P.elements) {
		let P = { ALT: buildElement(d, I, !0) }, L = getGuardCondition(I);
		L && (P.GATE = buildPredicate(L)), F.push(P);
	}
	let I = d.or++, L = (d, P) => `uGroup_${d}_${P.getRuleStack().join("-")}`, R = wrap(d, getGuardCondition(P), (P) => d.parser.alternatives(I, F.map((F, R) => {
		let z = { ALT: () => !0 }, B = d.parser;
		z.ALT = () => {
			if (F.ALT(P), !B.isRecording()) {
				let d = L(I, B);
				B.unorderedGroups.get(d) || B.unorderedGroups.set(d, []);
				let P = B.unorderedGroups.get(d);
				P?.[R] === void 0 && (P[R] = !0);
			}
		};
		let V = F.GATE;
		return V ? z.GATE = () => V(P) : z.GATE = () => !B.unorderedGroups.get(L(I, B))?.[R], z;
	})), "*");
	return (P) => {
		R(P), d.parser.isRecording() || d.parser.unorderedGroups.delete(L(I, d.parser));
	};
}
function buildGroup(d, P) {
	let F = P.elements.map((P) => buildElement(d, P));
	return (d) => F.forEach((P) => P(d));
}
function getGuardCondition(d) {
	if (isGroup(d)) return d.guardCondition;
}
function buildCrossReference(d, P, F = P.terminal) {
	if (F) if (isRuleCall(F) && isParserRule(F.rule.ref)) {
		let I = F.rule.ref, L = d.subrule++, R;
		return (F) => {
			R ??= getRule(d, I), d.parser.subrule(L, R, !1, P, F);
		};
	} else if (isRuleCall(F) && isTerminalRule(F.rule.ref)) {
		let I = d.consume++, L = getToken(d, F.rule.ref.name);
		return () => d.parser.consume(I, L, P);
	} else if (isKeyword(F)) {
		let I = d.consume++, L = getToken(d, F.value);
		return () => d.parser.consume(I, L, P);
	} else throw Error("Could not build cross reference parser");
	else {
		if (!P.type.ref) throw Error("Could not resolve reference to type: " + P.type.$refText);
		let F = findNameAssignment(P.type.ref)?.terminal;
		if (!F) throw Error("Could not find name assignment for type: " + getTypeName(P.type.ref));
		return buildCrossReference(d, P, F);
	}
}
function buildKeyword(d, P) {
	let F = d.consume++, I = d.tokens[P.value];
	if (!I) throw Error("Could not find token for keyword: " + P.value);
	return () => d.parser.consume(F, I, P);
}
function wrap(d, P, F, I) {
	let L = P && buildPredicate(P);
	if (!I) if (L) {
		let P = d.or++;
		return (I) => d.parser.alternatives(P, [{
			ALT: () => F(I),
			GATE: () => L(I)
		}, {
			ALT: EMPTY_ALT(),
			GATE: () => !L(I)
		}]);
	} else return F;
	if (I === "*") {
		let P = d.many++;
		return (I) => d.parser.many(P, {
			DEF: () => F(I),
			GATE: L ? () => L(I) : void 0
		});
	} else if (I === "+") {
		let P = d.many++;
		if (L) {
			let I = d.or++;
			return (R) => d.parser.alternatives(I, [{
				ALT: () => d.parser.atLeastOne(P, { DEF: () => F(R) }),
				GATE: () => L(R)
			}, {
				ALT: EMPTY_ALT(),
				GATE: () => !L(R)
			}]);
		} else return (I) => d.parser.atLeastOne(P, { DEF: () => F(I) });
	} else if (I === "?") {
		let P = d.optional++;
		return (I) => d.parser.optional(P, {
			DEF: () => F(I),
			GATE: L ? () => L(I) : void 0
		});
	} else assertUnreachable(I);
}
function getRule(d, P) {
	let F = getRuleName(d, P), I = d.parser.getRule(F);
	if (!I) throw Error(`Rule "${F}" not found."`);
	return I;
}
function getRuleName(d, F) {
	if (isAbstractParserRule(F)) return F.name;
	if (d.ruleNames.has(F)) return d.ruleNames.get(F);
	{
		let P = F, L = P.$container, R = F.$type;
		for (; !isParserRule(L);) (isGroup(L) || isAlternatives(L) || isUnorderedGroup(L)) && (R = L.elements.indexOf(P).toString() + ":" + R), P = L, L = L.$container;
		return R = L.name + ":" + R, d.ruleNames.set(F, R), R;
	}
}
function getToken(d, P) {
	let F = d.tokens[P];
	if (!F) throw Error(`Token "${P}" not found."`);
	return F;
}
export { createParser };
