import { getMatches } from "../file-matching.js";
import { defineSource } from "../source.js";
import { nonEmptyString, optionalUrl, stringArray } 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/openframeworks-install-xml.ts
/**
* Source and parser for legacy openFrameworks addon `install.xml` files.
*
* This is a legacy format predating `addon_config.mk`. The root element is
* `<install>`, with flat metadata elements (`<name>`, `<version>`, `<author>`,
* etc.) followed by build configuration in an `<add>` block.
*
* Since `install.xml` is a generic filename, the parser validates that the
* content is actually an openFrameworks addon by checking for the `<install>`
* root element and the presence of "addons" in the file content.
*
* Uses `fast-xml-parser` with attribute parsing enabled to read `<lib os="...">`
* attributes for operating system inference.
*/
const openframeworksInstallXmlSchema = z.object({
	author: nonEmptyString,
	codeUrl: optionalUrl,
	description: nonEmptyString,
	downloadUrl: optionalUrl,
	name: nonEmptyString,
	operatingSystems: stringArray,
	requirements: stringArray,
	siteUrl: optionalUrl,
	url: optionalUrl,
	version: nonEmptyString
});
/**
* Map `<lib os="...">` attribute values to human-readable OS names.
*/
const LIB_OS_MAP = {
	linux: "Linux",
	mac: "macOS",
	win32: "Windows"
};
/**
* Parse a legacy openFrameworks `install.xml` content string into a structured object.
* Returns undefined if the XML is malformed, missing the `<install>` root element,
* or does not appear to be an openFrameworks addon.
*/
function parse(content) {
	if (!content.toLowerCase().includes("addons")) return;
	const fixedContent = content.replaceAll("<[CDATA[", "<![CDATA[");
	const parser = new XMLParser({
		attributeNamePrefix: "@_",
		ignoreAttributes: false,
		parseTagValue: false
	});
	let data;
	try {
		const parsed = parser.parse(fixedContent);
		if (!is.plainObject(parsed)) return void 0;
		data = parsed;
	} catch {
		return;
	}
	if (!is.plainObject(data.install)) return void 0;
	const { install } = data;
	return openframeworksInstallXmlSchema.parse({
		author: getString(install.author),
		codeUrl: getString(install.code_url),
		description: getString(install.description),
		downloadUrl: getString(install.download_url),
		name: getString(install.name),
		operatingSystems: parseOperatingSystems(install),
		requirements: parseRequirements(install),
		siteUrl: getString(install.site_url),
		url: getString(install.url),
		version: getString(install.version)
	});
}
/**
* Get a trimmed non-empty string from a parsed XML value.
* Returns undefined for empty strings, non-strings, or whitespace-only values.
*/
function getString(value) {
	if (typeof value !== "string") return void 0;
	const trimmed = value.trim();
	return trimmed.length > 0 ? trimmed : void 0;
}
/**
* Extract software requirements from `<requires>`.
* Handles three variants:
* 1. Empty — skip
* 2. Free text — emit as-is
* 3. Structured `<addon>` children — emit each separately
*/
function parseRequirements(install) {
	const { requires } = install;
	if (requires === void 0 || requires === null) return [];
	if (typeof requires === "string") {
		const trimmed = requires.trim();
		return trimmed.length > 0 ? [trimmed] : [];
	}
	if (is.plainObject(requires)) {
		const results = [];
		for (const addon of ensureArray(requires.addon)) {
			const name = getString(addon);
			if (name) results.push(name);
		}
		return results;
	}
	return [];
}
/**
* Extract operating system information from `<lib os="...">` attributes
* found within `<add><link>` sections.
*/
function parseOperatingSystems(install) {
	if (!is.plainObject(install.add)) return [];
	const { add } = install;
	if (!is.plainObject(add.link)) return [];
	const { link } = add;
	const results = [];
	const seen = /* @__PURE__ */ new Set();
	for (const library of ensureArray(link.lib)) {
		if (!is.plainObject(library)) continue;
		const os = getString(library["@_os"]);
		if (os) {
			const mapped = LIB_OS_MAP[os.toLowerCase()] ?? os;
			if (!seen.has(mapped)) {
				seen.add(mapped);
				results.push(mapped);
			}
		}
	}
	return results;
}
const openframeworksInstallXmlSource = defineSource({
	async discover(context) {
		return getMatches(context.options, ["install.xml"]);
	},
	key: "openframeworksInstallXml",
	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 { openframeworksInstallXmlSource };
