import { getMatches } from "../file-matching.js";
import { parseProperties } from "../parsers/properties-parser.js";
import { defineSource } from "../source.js";
import { nonEmptyString, optionalUrl, stringArray } from "../utilities/schema-primitives.js";
import { splitCommaSeparated } from "../utilities/template-helpers.js";
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";
import { z } from "zod";
//#region src/lib/sources/arduino-library-properties.ts
const arduinoLibraryPropertiesPersonEntrySchema = z.object({
	email: z.string().optional(),
	name: z.string()
});
const arduinoLibraryPropertiesDependencyEntrySchema = z.object({
	name: z.string(),
	versionConstraint: z.string().optional()
});
/** Canonical Arduino library categories. */
const CANONICAL_CATEGORIES = [
	"Communication",
	"Data Processing",
	"Data Storage",
	"Device Control",
	"Display",
	"Other",
	"Sensors",
	"Signal Input/Output",
	"Timing",
	"Uncategorized"
];
/** Map from letters-only lowercase to canonical category form. */
const CATEGORY_MAP = new Map(CANONICAL_CATEGORIES.map((cat) => [cat.replaceAll(/[^a-z]/gi, "").toLowerCase(), cat]));
CATEGORY_MAP.set("sensor", "Sensors");
const arduinoLibraryPropertiesSchema = z.object({
	architectures: stringArray,
	authors: z.array(arduinoLibraryPropertiesPersonEntrySchema),
	category: z.enum(CANONICAL_CATEGORIES).optional(),
	depends: z.array(arduinoLibraryPropertiesDependencyEntrySchema),
	email: nonEmptyString,
	includes: stringArray,
	license: nonEmptyString,
	maintainer: arduinoLibraryPropertiesPersonEntrySchema.optional(),
	name: nonEmptyString,
	paragraph: nonEmptyString,
	raw: z.record(z.string(), z.string()),
	repository: optionalUrl,
	sentence: nonEmptyString,
	url: optionalUrl,
	version: nonEmptyString
});
/**
* Parse an Arduino `library.properties` content string into a structured object.
*/
function parse(content) {
	const raw = parseProperties(content);
	const includesValue = get(raw, "includes");
	return arduinoLibraryPropertiesSchema.parse({
		architectures: splitCommaSeparated(get(raw, "architectures") ?? "*"),
		authors: parsePersonList(get(raw, "author") ?? ""),
		category: normalizeCategory(get(raw, "category")),
		depends: parseDependencies(get(raw, "depends") ?? get(raw, "dependencies") ?? ""),
		email: nonEmpty(get(raw, "email")),
		includes: includesValue ? splitCommaSeparated(includesValue) : [],
		license: nonEmpty(get(raw, "license")),
		maintainer: parsePersonList(get(raw, "maintainer") ?? "")[0],
		name: nonEmpty(get(raw, "name")),
		paragraph: nonEmpty(get(raw, "paragraph")),
		raw,
		repository: nonEmpty(get(raw, "repository")),
		sentence: nonEmpty(get(raw, "sentence")),
		url: nonEmpty(get(raw, "url")),
		version: nonEmpty(get(raw, "version"))
	});
}
/** Helper to get a value from the raw record. */
function get(raw, key) {
	return raw[key];
}
/** Return a trimmed string, or undefined if empty/whitespace-only. */
function nonEmpty(value) {
	if (value === void 0) return void 0;
	const trimmed = value.trim();
	return trimmed.length > 0 ? trimmed : void 0;
}
/**
* Normalize a category string to the canonical Arduino category.
* Strips all non-letter characters, lowercases, and matches against the canonical map.
* For comma-separated values (e.g. "Sensors, Timing"), takes the first valid match.
*/
function normalizeCategory(value) {
	if (value === void 0) return void 0;
	const trimmed = value.trim();
	if (trimmed.length === 0) return void 0;
	for (const part of trimmed.split(",")) {
		const key = part.replaceAll(/[^a-z]/gi, "").toLowerCase();
		if (key.length === 0) continue;
		const canonical = CATEGORY_MAP.get(key);
		if (canonical) return canonical;
	}
}
/**
* Parse a comma-separated list of `Name <email>` entries into PersonEntry[].
*/
function parsePersonList(value) {
	const trimmed = value.trim();
	if (trimmed.length === 0) return [];
	const results = [];
	for (const entry of trimmed.split(",")) {
		const trimmedEntry = entry.trim();
		const bracketIndex = trimmedEntry.indexOf("<");
		if (bracketIndex !== -1) {
			const closeBracket = trimmedEntry.indexOf(">", bracketIndex);
			if (closeBracket !== -1) {
				const name = trimmedEntry.slice(0, bracketIndex).trim();
				const email = trimmedEntry.slice(bracketIndex + 1, closeBracket).trim();
				if (name.length > 0 || email.length > 0) results.push({
					email: email.length > 0 ? email : void 0,
					name
				});
				continue;
			}
		}
		if (trimmedEntry.length > 0) results.push({
			email: void 0,
			name: trimmedEntry
		});
	}
	return results;
}
/**
* Parse a comma-separated list of dependencies with optional version constraints.
* e.g. "ArduinoHttpClient (>=1.0.0), ArduinoJson" →
*   [{ name: "ArduinoHttpClient", versionConstraint: ">=1.0.0" }, ...]
*/
function parseDependencies(value) {
	const trimmed = value.trim();
	if (trimmed.length === 0) return [];
	const results = [];
	for (const entry of trimmed.split(",")) {
		const trimmedEntry = entry.trim();
		if (trimmedEntry.length === 0) continue;
		const parenIndex = trimmedEntry.indexOf("(");
		if (parenIndex !== -1) {
			const closeParen = trimmedEntry.indexOf(")", parenIndex);
			if (closeParen !== -1) {
				const name = trimmedEntry.slice(0, parenIndex).trim();
				const constraint = trimmedEntry.slice(parenIndex + 1, closeParen).trim();
				if (name.length > 0) {
					results.push({
						name,
						versionConstraint: constraint.length > 0 ? constraint : void 0
					});
					continue;
				}
			}
		}
		results.push({
			name: trimmedEntry,
			versionConstraint: void 0
		});
	}
	return results;
}
/**
* Arduino-specific fields that distinguish library.properties from
* Processing's identically-named format.
*/
const ARDUINO_SPECIFIC_FIELDS = new Set([
	"architectures",
	"depends",
	"dot_a_linkage",
	"maintainer"
]);
/** Processing-exclusive fields that rule out Arduino. */
const PROCESSING_EXCLUSIVE_FIELDS = new Set([
	"authors",
	"minrevision",
	"prettyversion"
]);
/**
* Validate that a library.properties file is Arduino (not Processing).
* Requires name=, version=, author= and either an Arduino-specific field
* or no Processing-exclusive fields.
*/
function isArduinoLibraryProperties(content) {
	const raw = parseProperties(content);
	const keys = new Set(Object.keys(raw));
	if (!keys.has("name") || !keys.has("version") || !keys.has("author")) return false;
	for (const field of ARDUINO_SPECIFIC_FIELDS) if (keys.has(field)) return true;
	for (const field of PROCESSING_EXCLUSIVE_FIELDS) if (keys.has(field)) return false;
	return true;
}
const arduinoLibraryPropertiesSource = defineSource({
	async discover(context) {
		return getMatches(context.options, ["library.properties"]);
	},
	key: "arduinoLibraryProperties",
	async parse(input, context) {
		const content = await readFile(resolve(context.options.path, input), "utf8");
		if (isArduinoLibraryProperties(content)) return {
			data: parse(content),
			source: input
		};
	},
	phase: 1
});
//#endregion
export { arduinoLibraryPropertiesSource };
