import { getRubyLanguage, initParser } from "../utilities/tree-sitter-wasm.js";
import is from "@sindresorhus/is";
//#region src/lib/parsers/gemspec-parser.ts
function emptySpec() {
	return {
		authors: [],
		bindir: void 0,
		cert_chain: [],
		dependencies: [],
		description: void 0,
		email: void 0,
		executables: [],
		extensions: [],
		extra: {},
		extra_rdoc_files: [],
		files: [],
		homepage: void 0,
		license: void 0,
		licenses: [],
		metadata: {},
		name: void 0,
		platform: void 0,
		post_install_message: void 0,
		rdoc_options: [],
		require_paths: [],
		required_ruby_version: void 0,
		required_rubygems_version: void 0,
		signing_key: void 0,
		summary: void 0,
		test_files: [],
		version: void 0
	};
}
/** Filter nulls from web-tree-sitter's `namedChildren` array. */
function children(node) {
	return node.namedChildren.filter((c) => c !== null);
}
/** Methods that return the receiver unchanged — safe to unwrap. */
const IDENTITY_METHODS = new Set([
	"-@",
	"dup",
	"freeze"
]);
/** Extract the raw string value from a tree-sitter string/symbol node. */
function extractString(node) {
	switch (node.type) {
		case "call": {
			const method = node.childForFieldName("method");
			if (method && IDENTITY_METHODS.has(method.text)) {
				const receiver = node.childForFieldName("receiver");
				if (receiver) return extractString(receiver);
			}
			return;
		}
		case "float":
		case "integer": return node.text;
		case "heredoc_body": return node.text.trim();
		case "simple_symbol": return node.text.replace(/^:/, "");
		case "string":
		case "string_content": {
			const parts = children(node).filter((c) => c.type === "string_content");
			if (parts.length > 0) return parts.map((p) => p.text).join("");
			return node.text.replaceAll(/^["']|["']$/g, "");
		}
		default: return;
	}
}
/** Extract a string array from an array node like `["a", "b"]`. */
function extractStringArray(node) {
	if (node.type === "array") return children(node).map((element) => extractString(element)).filter((s) => s !== void 0);
	if (node.type === "string_array") return children(node).map((c) => c.type === "bare_string" ? c.text : c.text);
	const single = extractString(node);
	return single === void 0 ? [] : [single];
}
/**
* Attempt to extract a usable value from an arbitrary RHS node.
* Returns string | string[] | null — we intentionally skip expressions
* we can't statically evaluate (method calls, constants, etc.).
*/
function extractValue(node) {
	if (node.type === "array" || node.type === "string_array") return extractStringArray(node);
	if (node.type === "call") {
		const method = node.childForFieldName("method");
		if (method && IDENTITY_METHODS.has(method.text)) {
			const receiver = node.childForFieldName("receiver");
			if (receiver) return extractValue(receiver);
		}
	}
	if (node.type === "true") return "true";
	if (node.type === "false") return "false";
	if (node.type === "nil") return void 0;
	return extractString(node);
}
/** Resolve the attribute name from the LHS of `spec.foo = ...` */
function resolveAttribute(node) {
	if (node.type === "call") return node.childForFieldName("method")?.text ?? void 0;
}
const DEP_METHODS = {
	add_dependency: "runtime",
	add_development_dependency: "development",
	add_runtime_dependency: "runtime"
};
function tryParseDependency(node) {
	if (node.type !== "call" && node.type !== "method_call") return void 0;
	const methodNode = node.childForFieldName("method");
	if (!methodNode) return void 0;
	let methodName;
	if (methodNode.type === "call") methodName = methodNode.childForFieldName("method")?.text ?? void 0;
	else if (methodNode.type === "identifier") methodName = methodNode.text;
	else methodName = methodNode.text;
	if (!methodName) {
		const topMethod = node.childForFieldName("method");
		if (topMethod?.type === "identifier") methodName = topMethod.text;
	}
	if (!methodName || !DEP_METHODS[methodName]) {
		for (const m of Object.keys(DEP_METHODS)) if (node.text.includes(`.${m}`)) {
			methodName = m;
			break;
		}
	}
	if (!methodName || !DEP_METHODS[methodName]) return void 0;
	const depType = DEP_METHODS[methodName];
	const arguments_ = node.childForFieldName("arguments");
	if (!arguments_) return void 0;
	const argumentNodes = children(arguments_);
	if (argumentNodes.length === 0) return void 0;
	const depName = extractString(argumentNodes[0]);
	if (!depName) return void 0;
	return {
		name: depName,
		requirements: argumentNodes.slice(1).map((element) => extractString(element)).filter((s) => s !== void 0),
		type: depType
	};
}
function extractHash(node) {
	const result = {};
	if (node.type !== "hash") return result;
	for (const pair of children(node)) {
		if (pair.type !== "pair") continue;
		const key = pair.childForFieldName("key");
		const value = pair.childForFieldName("value");
		if (!key || !value) continue;
		const k = extractString(key);
		const v = extractString(value);
		if (k && v) result[k] = v;
	}
	return result;
}
function setStringAttribute(spec, key, value) {
	Object.assign(spec, { [key]: value });
}
function setArrayAttribute(spec, key, value) {
	Object.assign(spec, { [key]: value });
}
/**
* Parse a `.gemspec` file's contents and return a plain object with the
* extracted fields.
*
* Uses tree-sitter with the Ruby grammar to walk the AST, so it can handle
* most real-world gemspec patterns without executing Ruby.
*
* Fields that reference Ruby constants (e.g. `Foo::VERSION`) or dynamic
* expressions (e.g. `Dir.glob(...)`) will be `null` / empty — the parser
* only extracts statically determinable values.
*/
async function parseGemspec(source) {
	const parser = await initParser();
	const ruby = await getRubyLanguage();
	parser.setLanguage(ruby);
	const tree = parser.parse(source);
	if (!tree) throw new Error("Failed to parse gemspec source");
	const spec = emptySpec();
	/** Map of simple attribute names → setter logic */
	const STRING_ATTRS = new Set([
		"bindir",
		"description",
		"homepage",
		"license",
		"name",
		"platform",
		"post_install_message",
		"required_ruby_version",
		"required_rubygems_version",
		"signing_key",
		"summary",
		"version"
	]);
	const ARRAY_ATTRS = new Set([
		"authors",
		"cert_chain",
		"executables",
		"extensions",
		"extra_rdoc_files",
		"files",
		"licenses",
		"rdoc_options",
		"require_paths",
		"test_files"
	]);
	function visit(node) {
		if (node.type === "assignment") {
			const lhs = node.childForFieldName("left");
			const rhs = node.childForFieldName("right");
			if (!lhs || !rhs) {
				visitChildren(node);
				return;
			}
			const attribute = resolveAttribute(lhs);
			if (!attribute) {
				visitChildren(node);
				return;
			}
			if (attribute === "email") {
				const value = extractValue(rhs);
				if (value !== void 0) spec.email = value;
				return;
			}
			if (attribute === "metadata") {
				if (rhs.type === "hash") spec.metadata = {
					...is.plainObject(spec.metadata) ? spec.metadata : {},
					...extractHash(rhs)
				};
				return;
			}
			if (STRING_ATTRS.has(attribute)) {
				const value = extractString(rhs);
				if (value !== void 0) setStringAttribute(spec, attribute, value);
				return;
			}
			if (ARRAY_ATTRS.has(attribute)) {
				const array = extractStringArray(rhs);
				if (array.length > 0) setArrayAttribute(spec, attribute, array);
				return;
			}
			const value = extractValue(rhs);
			if (value !== void 0 && is.plainObject(spec.extra)) spec.extra[attribute] = value;
			return;
		}
		if (node.type === "call" || node.type === "method_call") {
			const dep = tryParseDependency(node);
			if (dep) {
				if (Array.isArray(spec.dependencies)) spec.dependencies.push(dep);
				return;
			}
		}
		if (node.type === "element_assignment" || node.type === "indexing") {}
		visitChildren(node);
	}
	function visitChildren(node) {
		for (const child of children(node)) visit(child);
	}
	visit(tree.rootNode);
	return spec;
}
//#endregion
export { parseGemspec };
