import { getMatches } from "../file-matching.js";
import { defineSource } from "../source.js";
import { nonEmptyString, optionalUrl } from "../utilities/schema-primitives.js";
import { ensureArray } from "../utilities/template-helpers.js";
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";
import is from "@sindresorhus/is";
import { z } from "zod";
import { XMLParser } from "fast-xml-parser";
//#region src/lib/sources/java-pom-xml.ts
/**
* Source and parser for Maven `pom.xml` files.
*
* Extracts project metadata from Maven POM (Project Object Model) files,
* including coordinates (groupId/artifactId), developers, contributors,
* licenses, dependencies (with test scope separation), SCM info, CI/issue
* management URLs, and organization details.
*
* Uses `fast-xml-parser` with namespace prefix removal and attribute ignoring.
*/
const pomXmlPersonEntrySchema = z.object({
	email: z.string().optional(),
	name: z.string(),
	organization: z.string().optional(),
	url: z.string().optional()
});
const pomXmlLicenseEntrySchema = z.object({
	name: z.string().optional(),
	url: z.string().optional()
});
const pomXmlDependencyEntrySchema = z.object({
	artifactId: z.string(),
	groupId: z.string(),
	version: z.string().optional()
});
const pomXmlOrganizationSchema = z.object({
	name: z.string(),
	url: z.string().optional()
});
const pomXmlSchema = z.object({
	artifactId: nonEmptyString,
	ciManagementUrl: optionalUrl,
	contributors: z.array(pomXmlPersonEntrySchema),
	dependencies: z.array(pomXmlDependencyEntrySchema),
	description: nonEmptyString,
	devDependencies: z.array(pomXmlDependencyEntrySchema),
	developers: z.array(pomXmlPersonEntrySchema),
	groupId: nonEmptyString,
	identifier: nonEmptyString,
	inceptionYear: nonEmptyString,
	issueManagementUrl: optionalUrl,
	javaVersion: nonEmptyString,
	licenses: z.array(pomXmlLicenseEntrySchema),
	name: nonEmptyString,
	organization: pomXmlOrganizationSchema.optional(),
	scmUrl: optionalUrl,
	url: optionalUrl,
	version: nonEmptyString
});
/**
* Parse a Maven `pom.xml` content string into a structured object.
* Returns undefined if the XML is malformed or missing the `<project>` root element.
*/
function parse(content) {
	const parser = new XMLParser({
		ignoreAttributes: true,
		parseTagValue: false,
		removeNSPrefix: true
	});
	let data;
	try {
		const parsed = parser.parse(content);
		if (!is.plainObject(parsed)) return void 0;
		data = parsed;
	} catch {
		return;
	}
	if (!is.plainObject(data.project)) return void 0;
	const { project } = data;
	const groupId = getString(project.groupId);
	const artifactId = getString(project.artifactId);
	const { dependencies, devDependencies } = parseDependencies(project);
	return pomXmlSchema.parse({
		artifactId,
		ciManagementUrl: getNestedUrl(project.ciManagement),
		contributors: parsePersonEntries(project.contributors, "contributor"),
		dependencies,
		description: getString(project.description),
		devDependencies,
		developers: parsePersonEntries(project.developers, "developer"),
		groupId,
		identifier: groupId && artifactId ? `${groupId}.${artifactId}` : void 0,
		inceptionYear: getString(project.inceptionYear),
		issueManagementUrl: getNestedUrl(project.issueManagement),
		javaVersion: parseJavaVersion(project),
		licenses: parseLicenses(project),
		name: resolveName(project, groupId, artifactId),
		organization: parseOrganization(project),
		scmUrl: parseScmUrl(project),
		url: getString(project.url),
		version: getString(project.version)
	});
}
/**
* Get a trimmed non-empty string from a parsed XML value.
* Returns undefined for empty strings, non-strings, whitespace-only, or Maven variable references.
*/
function getString(value) {
	if (typeof value !== "string") return void 0;
	const trimmed = value.trim();
	if (trimmed.length === 0) return void 0;
	return trimmed;
}
/**
* Get a string, additionally filtering out Maven variable references ($).
*/
function getCleanString(value) {
	const s = getString(value);
	if (s?.includes("$")) return void 0;
	return s;
}
/**
* Resolve Maven variable references in a project name.
*/
function resolveName(project, groupId, artifactId) {
	const name = getString(project.name);
	if (!name) return void 0;
	let resolved = name;
	if (groupId) resolved = resolved.replaceAll("${project.groupId}", groupId);
	if (artifactId) resolved = resolved.replaceAll("${project.artifactId}", artifactId);
	return resolved;
}
/**
* Extract a URL from a nested object (e.g. `<ciManagement><url>...</url></ciManagement>`).
* Filters out Maven variable references.
*/
function getNestedUrl(container) {
	if (!is.plainObject(container)) return void 0;
	return getCleanString(container.url);
}
/**
* Parse person entries (developers or contributors) from POM XML.
*/
function parsePersonEntries(container, childKey) {
	if (!is.plainObject(container)) return [];
	const children = container[childKey];
	const results = [];
	for (const entry of ensureArray(children)) {
		if (!is.plainObject(entry)) continue;
		const name = getString(entry.name);
		if (!name) continue;
		results.push({
			email: getString(entry.email),
			name,
			organization: getString(entry.organization),
			url: getString(entry.url)
		});
	}
	return results;
}
/**
* Parse license entries from `<licenses><license>...</license></licenses>`.
*/
function parseLicenses(project) {
	if (!is.plainObject(project.licenses)) return [];
	const results = [];
	for (const entry of ensureArray(project.licenses.license)) {
		if (!is.plainObject(entry)) continue;
		const name = getString(entry.name);
		const url = getString(entry.url);
		if (name ?? url) results.push({
			name,
			url
		});
	}
	return results;
}
/**
* Parse dependencies, separating test-scope into devDependencies.
*/
function parseDependencies(project) {
	const dependencies = [];
	const devDependencies = [];
	if (!is.plainObject(project.dependencies)) return {
		dependencies,
		devDependencies
	};
	for (const entry of ensureArray(project.dependencies.dependency)) {
		if (!is.plainObject(entry)) continue;
		const groupId = getString(entry.groupId);
		const artifactId = getString(entry.artifactId);
		if (!groupId || !artifactId) continue;
		const dep = {
			artifactId,
			groupId,
			version: getCleanString(entry.version)
		};
		if (getString(entry.scope) === "test") devDependencies.push(dep);
		else dependencies.push(dep);
	}
	return {
		dependencies,
		devDependencies
	};
}
/**
* Parse SCM URL, filtering out Maven variable references.
*/
function parseScmUrl(project) {
	if (!is.plainObject(project.scm)) return void 0;
	return getCleanString(project.scm.url);
}
/**
* Parse organization from `<organization>` element.
*/
function parseOrganization(project) {
	if (!is.plainObject(project.organization)) return void 0;
	const name = getString(project.organization.name);
	if (!name) return void 0;
	return {
		name,
		url: getString(project.organization.url)
	};
}
/**
* Extract Java version from project properties.
* Checks `java.version`, `maven.compiler.source`, and `java.compiler.source`.
*/
function parseJavaVersion(project) {
	if (!is.plainObject(project.properties)) return void 0;
	return getCleanString(project.properties["java.version"]) ?? getCleanString(project.properties["maven.compiler.source"]) ?? getCleanString(project.properties["java.compiler.source"]);
}
const javaPomXmlSource = defineSource({
	async discover(context) {
		return getMatches(context.options, ["pom.xml"]);
	},
	key: "javaPomXml",
	async parse(input, context) {
		const data = parse(await readFile(resolve(context.options.path, input), "utf8"));
		if (data !== void 0) return {
			data,
			source: input
		};
	},
	phase: 1
});
//#endregion
export { javaPomXmlSource };
