const require_rules = require('./rules-D4Fn94Uc.cjs');

//#region src/core/analyze.ts
function analyze(sourceFile, position, context) {
	const { typescript: ts, languageService, toSourceRange, signals, propertyAccesses, propertyCalls, functionCalls } = context;
	const signal = findSignalByBindingRange(position) ?? findSignalByCallbackRange(position);
	if (!signal) return;
	const dependencies = findDependencies(signal);
	const dependents = findDependents(signal);
	if (!signal.isDependency && !dependencies.length || !signal.isDependent && !dependents.length) return;
	const dependencyRanges = [];
	const dependentRanges = [];
	for (const dependency of dependencies) if (ts.isBlock(dependency.ast) && dependency.ast.statements.length) {
		const { statements } = dependency.ast;
		const sourceRange = toSourceRange(statements[0].getStart(sourceFile), statements.at(-1).end);
		if (sourceRange) dependencyRanges.push(sourceRange);
	} else dependencyRanges.push({
		pos: dependency.pos,
		end: dependency.end
	});
	for (const { callback } of dependents) {
		if (!callback) continue;
		if (ts.isBlock(callback.ast) && callback.ast.statements.length) {
			const { statements } = callback.ast;
			const sourceRange = toSourceRange(statements[0].getStart(sourceFile), statements.at(-1).end);
			if (sourceRange) dependentRanges.push(sourceRange);
		} else dependentRanges.push({
			pos: callback.pos,
			end: callback.end
		});
	}
	return {
		dependencyRanges,
		dependentRanges
	};
	function findDependencies(signal$1, visited = /* @__PURE__ */ new Set()) {
		if (visited.has(signal$1)) return [];
		visited.add(signal$1);
		const nodes = [];
		let hasDependency = signal$1.isDependency;
		if (signal$1.accessor) {
			const { requireAccess } = signal$1.accessor;
			visit(signal$1.accessor, requireAccess);
		}
		if (!hasDependency) return [];
		return nodes;
		function visit(node, requireAccess, parentIsPropertyAccess = false) {
			if (!requireAccess) {
				if (!parentIsPropertyAccess && ts.isIdentifier(node.ast)) {
					const definition = languageService.getDefinitionAtPosition(sourceFile.fileName, node.pos) ?? [];
					for (const info of definition) {
						if (info.fileName !== sourceFile.fileName) continue;
						const signal$2 = findSignalByBindingRange(info.textSpan.start);
						if (!signal$2) continue;
						if (signal$2.binding) {
							nodes.push(signal$2.binding);
							hasDependency ||= signal$2.isDependency;
						}
						if (signal$2.callback) nodes.push(signal$2.callback);
						const deps = findDependencies(signal$2, visited);
						nodes.push(...deps);
						hasDependency ||= deps.length > 0;
					}
				}
			} else if (ts.isPropertyAccessExpression(node.ast) || ts.isElementAccessExpression(node.ast) || ts.isCallExpression(node.ast)) {
				const definition = languageService.getDefinitionAtPosition(sourceFile.fileName, node.pos) ?? [];
				for (const info of definition) {
					if (info.fileName !== sourceFile.fileName) continue;
					const signal$2 = findSignalByBindingRange(info.textSpan.start);
					if (!signal$2) continue;
					const oldSize = nodes.length;
					for (const accessType of signal$2.binding?.accessTypes ?? []) if (ts.isPropertyAccessExpression(node.ast)) {
						if (accessType === ".*" && node.ast.name.text !== "" || accessType === "." + node.ast.name.text) {
							nodes.push(signal$2.binding);
							hasDependency ||= signal$2.isDependency;
						}
					} else if (ts.isElementAccessExpression(node.ast)) {
						if (accessType === ".*") {
							nodes.push(signal$2.binding);
							hasDependency ||= signal$2.isDependency;
						}
					} else if (ts.isCallExpression(node.ast)) {
						if (accessType.endsWith("()")) {
							nodes.push(signal$2.binding);
							hasDependency ||= signal$2.isDependency;
						}
					}
					if (nodes.length > oldSize) {
						if (signal$2.callback) nodes.push(signal$2.callback);
						const deps = findDependencies(signal$2, visited);
						nodes.push(...deps);
						hasDependency ||= deps.length > 0;
					}
				}
			}
			node.ast.forEachChild((child) => {
				const childNode = createTsNode(child);
				if (childNode) visit(childNode, ts.isFunctionExpression(node.ast) || ts.isArrowFunction(node.ast) || requireAccess, ts.isPropertyAccessExpression(node.ast) || ts.isElementAccessExpression(node.ast));
			});
		}
	}
	function findDependents(signal$1, visited = /* @__PURE__ */ new Set()) {
		if (!signal$1.binding) return [];
		if (visited.has(signal$1)) return [];
		visited.add(signal$1);
		const references = languageService.findReferences(sourceFile.fileName, signal$1.binding.pos);
		if (!references) return [];
		const result = [];
		for (const entry of references.flatMap((symbol) => symbol.references)) {
			if (entry.fileName !== sourceFile.fileName) continue;
			const effect = findSignalByAccessorRange(entry.textSpan.start);
			if (effect?.accessor) {
				let match = false;
				if (effect.accessor.requireAccess) for (const accessType of signal$1.binding.accessTypes) if (accessType === ".*") match ||= propertyAccesses.has(entry.textSpan.start + entry.textSpan.length);
				else if (accessType === "()") match ||= functionCalls.has(entry.textSpan.start + entry.textSpan.length);
				else if (accessType.endsWith("()")) {
					const property = propertyCalls.get(entry.textSpan.start + entry.textSpan.length);
					match ||= "." + property + "()" === accessType;
				} else {
					const property = propertyAccesses.get(entry.textSpan.start + entry.textSpan.length);
					match ||= "." + property === accessType;
				}
				else match = true;
				if (match) {
					const dependents$1 = findDependents(effect, visited);
					result.push(...dependents$1);
					if (effect.isDependent || dependents$1.length) result.push(effect);
				}
			}
		}
		return result;
	}
	function findSignalByBindingRange(position$1) {
		return signals.find((ref) => ref.binding && ref.binding.pos <= position$1 && ref.binding.end >= position$1);
	}
	function findSignalByCallbackRange(position$1) {
		return signals.filter((ref) => ref.callback && ref.callback.pos <= position$1 && ref.callback.end >= position$1).sort((a, b) => a.callback.end - a.callback.pos - (b.callback.end - b.callback.pos))[0];
	}
	function findSignalByAccessorRange(position$1) {
		return signals.filter((ref) => ref.accessor && ref.accessor.pos <= position$1 && ref.accessor.end >= position$1).sort((a, b) => a.accessor.end - a.accessor.pos - (b.accessor.end - b.accessor.pos))[0];
	}
	function createTsNode(node) {
		const range = toSourceRange(node.getStart(sourceFile), node.end);
		if (range) return {
			...range,
			ast: node
		};
	}
}

//#endregion
//#region src/core/collect.ts
function collect(context, sourceFile) {
	return {
		signals: collectSignals(context, sourceFile),
		...collectAccesses(context, sourceFile)
	};
}
function collectSignals(context, sourceFile) {
	const { typescript: ts, rules, toSourceRange } = context;
	const signals = [];
	const resolved = /* @__PURE__ */ new Set();
	visit(sourceFile);
	return signals;
	function visit(node) {
		if (ts.isVariableDeclaration(node)) {
			const { name, initializer } = node;
			if (initializer) {
				if (ts.isCallExpression(initializer) && ts.isIdentifier(initializer.expression)) {
					for (const rule of rules) if (executeRule(rule, initializer, name)) {
						resolved.add(initializer);
						break;
					}
				} else if (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)) {
					const signal = createFunctionSignal(name, initializer.body);
					if (signal) signals.push(signal);
				}
			}
		} else if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && !resolved.has(node)) {
			for (const rule of rules) if (executeRule(rule, node)) break;
		} else if (ts.isFunctionDeclaration(node) && node.name && node.body) {
			const signal = createFunctionSignal(node.name, node.body);
			if (signal) signals.push(signal);
		}
		node.forEachChild(visit);
	}
	function executeRule(rule, expression, binding) {
		const signalMatches = [];
		const triggerMatches = [];
		const name = expression.expression.text;
		if (!isNameMatch(rule.name, name)) return;
		rule.resolve({
			binding,
			expression,
			typescript: ts,
			match(type, node, data) {
				if (type === "signal" && data) signalMatches.push([node, data]);
				else triggerMatches.push([node, type]);
			}
		});
		const signal = {};
		for (const [node, type] of triggerMatches) {
			let ast = node;
			let requireAccess = false;
			if (ts.isFunctionLike(node) && "body" in node && node.body) {
				ast = node.body;
				requireAccess = true;
			}
			const bodyNode = createTsNode(ast);
			if (!bodyNode) continue;
			if (type === "accessor" || type === "effect" && requireAccess) signal.accessor = {
				...bodyNode,
				requireAccess
			};
			if (type === "callback" || type === "effect") {
				signal.isDependent = true;
				signal.callback = { ...bodyNode };
			}
		}
		if (signalMatches.length) for (const [node, accessTypes] of signalMatches) {
			const bindingNode = createTsNode(node);
			if (!bindingNode) continue;
			const cosignal = { ...signal };
			cosignal.isDependency = true;
			cosignal.binding = {
				...bindingNode,
				accessTypes
			};
			signals.push(cosignal);
		}
		else signals.push(signal);
		return true;
	}
	function createFunctionSignal(name, body) {
		const nameNode = createTsNode(name);
		const bodyNode = createTsNode(body);
		if (nameNode && bodyNode) return {
			binding: {
				...nameNode,
				accessTypes: ["()"]
			},
			accessor: {
				...bodyNode,
				requireAccess: true
			},
			callback: bodyNode
		};
	}
	function createTsNode(node) {
		const range = toSourceRange(node.getStart(sourceFile), node.end);
		if (range) return {
			...range,
			ast: node
		};
	}
}
function collectAccesses(context, sourceFile) {
	const { typescript: ts, toSourceRange } = context;
	const propertyAccesses = /* @__PURE__ */ new Map();
	const propertyCalls = /* @__PURE__ */ new Map();
	const functionCalls = /* @__PURE__ */ new Set();
	visit(sourceFile);
	return {
		propertyAccesses,
		propertyCalls,
		functionCalls
	};
	function visit(node) {
		if (ts.isPropertyAccessExpression(node)) {
			const range = toSourceRange(node.expression.getStart(sourceFile), node.expression.end);
			if (range) propertyAccesses.set(range.end, node.name.text);
		} else if (ts.isElementAccessExpression(node)) {
			const range = toSourceRange(node.expression.getStart(sourceFile), node.expression.end);
			if (range) {
				const text = ts.isStringLiteralLike(node.argumentExpression) ? node.argumentExpression.text : "*";
				propertyAccesses.set(range.end, text);
			}
		} else if (ts.isCallExpression(node)) {
			const { expression } = node;
			if (ts.isPropertyAccessExpression(expression)) {
				const range = toSourceRange(expression.expression.getStart(sourceFile), expression.expression.end);
				if (range) propertyCalls.set(range.end, expression.name.text);
			} else if (ts.isIdentifier(expression)) {
				const range = toSourceRange(expression.getStart(sourceFile), expression.end);
				if (range) functionCalls.add(range.end);
			}
		}
		node.forEachChild(visit);
	}
}
function isNameMatch(ruleName, name) {
	return typeof ruleName === "string" ? ruleName === name : ruleName.test(name);
}

//#endregion
//#region src/core/utils.ts
const defaultToSourceRange = (pos, end) => ({
	pos,
	end
});

//#endregion
//#region src/core/index.ts
function createAnalyzer(options) {
	const { rules } = options;
	const cache = /* @__PURE__ */ new WeakMap();
	return {
		collect(sourceFile, options$1) {
			if (!cache.has(sourceFile)) {
				const context = {
					rules,
					toSourceRange: defaultToSourceRange,
					...options$1
				};
				cache.set(sourceFile, collect(context, sourceFile));
			}
			return cache.get(sourceFile);
		},
		analyze(sourceFile, position, options$1) {
			const context = {
				toSourceRange: defaultToSourceRange,
				...options$1,
				...this.collect(sourceFile, options$1)
			};
			return analyze(sourceFile, position, context);
		}
	};
}

//#endregion
exports.createAnalyzer = createAnalyzer;
exports.defineRules = require_rules.defineRules;