import { getMatches } from "../file-matching.js";
import { parseProperties } from "../parsers/properties-parser.js";
import { defineSource } from "../source.js";
import { nonEmptyString, optionalUrl } 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/processing-sketch-properties.ts
const processingSketchPropertiesAuthorEntrySchema = z.object({
	name: z.string(),
	url: z.string().optional()
});
const processingSketchPropertiesSchema = z.object({
	authors: z.array(processingSketchPropertiesAuthorEntrySchema),
	component: nonEmptyString,
	download: optionalUrl,
	main: nonEmptyString,
	manifestLabel: nonEmptyString,
	manifestOrientation: nonEmptyString,
	manifestPackage: nonEmptyString,
	manifestPermissions: z.array(z.string()),
	manifestSdkMin: z.number().optional(),
	manifestSdkTarget: z.number().optional(),
	manifestVersionCode: z.number().optional(),
	manifestVersionName: nonEmptyString,
	maxRevision: z.number(),
	minRevision: z.number(),
	mode: nonEmptyString,
	modeId: nonEmptyString,
	name: nonEmptyString,
	paragraph: nonEmptyString,
	prettyVersion: nonEmptyString,
	raw: z.record(z.string(), z.string()),
	sentence: nonEmptyString,
	templates: nonEmptyString,
	url: optionalUrl,
	version: z.number(),
	zipfilename: nonEmptyString
});
/**
* Parse a Processing `sketch.properties` content string into a structured object.
*/
function parse(content) {
	const raw = parseProperties(content);
	const versionRaw = raw.version ?? "0";
	const versionParsed = Number.parseInt(versionRaw, 10);
	const prettyVersionRaw = raw.prettyVersion;
	const prettyVersion = prettyVersionRaw ? stripInlineComment(prettyVersionRaw) : void 0;
	const minRevisionRaw = raw.minRevision;
	const minParsed = minRevisionRaw ? Number.parseInt(minRevisionRaw, 10) : 0;
	const maxRevisionRaw = raw.maxRevision;
	const maxParsed = maxRevisionRaw ? Number.parseInt(maxRevisionRaw, 10) : 0;
	const manifestVersionCodeRaw = raw["manifest.version.code"];
	const manifestVersionCode = manifestVersionCodeRaw ? Number.parseInt(manifestVersionCodeRaw, 10) : void 0;
	const manifestSdkTargetRaw = raw["manifest.sdk.target"];
	const manifestSdkTarget = manifestSdkTargetRaw ? Number.parseInt(manifestSdkTargetRaw, 10) : void 0;
	const manifestSdkMinRaw = raw["manifest.sdk.min"];
	const manifestSdkMin = manifestSdkMinRaw ? Number.parseInt(manifestSdkMinRaw, 10) : void 0;
	const manifestPermissions = splitCommaSeparated(raw["manifest.permissions"] ?? "");
	return processingSketchPropertiesSchema.parse({
		authors: parseAuthors(raw.authors ?? raw.authorList ?? ""),
		component: nonEmpty(raw.component),
		download: nonEmpty(unescapeUrl(raw.download ?? "")),
		main: nonEmpty(raw.main),
		manifestLabel: nonEmpty(raw["manifest.label"]),
		manifestOrientation: nonEmpty(raw["manifest.orientation"]),
		manifestPackage: nonEmpty(raw["manifest.package"]),
		manifestPermissions,
		manifestSdkMin: Number.isNaN(manifestSdkMin) ? void 0 : manifestSdkMin,
		manifestSdkTarget: Number.isNaN(manifestSdkTarget) ? void 0 : manifestSdkTarget,
		manifestVersionCode: Number.isNaN(manifestVersionCode) ? void 0 : manifestVersionCode,
		manifestVersionName: nonEmpty(raw["manifest.version.name"]),
		maxRevision: Number.isNaN(maxParsed) ? 0 : maxParsed,
		minRevision: Number.isNaN(minParsed) ? 0 : minParsed,
		mode: nonEmpty(raw.mode),
		modeId: nonEmpty(raw["mode.id"]),
		name: nonEmpty(raw.name),
		paragraph: nonEmpty(raw.paragraph),
		prettyVersion: nonEmpty(prettyVersion),
		raw,
		sentence: nonEmpty(raw.sentence),
		templates: nonEmpty(raw.templates),
		url: nonEmpty(unescapeUrl(raw.url ?? "")),
		version: Number.isNaN(versionParsed) ? 0 : versionParsed,
		zipfilename: nonEmpty(raw.zipfilename)
	});
}
/** 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;
}
/**
* Strip trailing inline comments from a value.
* Matches the pattern ` # comment text` (space-hash-space).
*/
function stripInlineComment(value) {
	const index = value.indexOf(" # ");
	return index === -1 ? value : value.slice(0, index).trim();
}
/**
* Unescape backslash-escaped colons in URLs.
*/
function unescapeUrl(value) {
	return value.replaceAll(String.raw`\:`, ":");
}
/**
* Parse an authors/authorList value into AuthorEntry[].
*/
function parseAuthors(value) {
	const trimmed = value.trim();
	if (trimmed.length === 0) return [];
	const results = [];
	let cursor = 0;
	while (cursor < trimmed.length) {
		const bracketStart = trimmed.indexOf("[", cursor);
		if (bracketStart === -1) {
			addPlainAuthors(trimmed.slice(cursor), results);
			break;
		}
		if (bracketStart > cursor) addPlainAuthors(trimmed.slice(cursor, bracketStart), results);
		const bracketEnd = trimmed.indexOf("]", bracketStart);
		if (bracketEnd === -1) {
			addPlainAuthors(trimmed.slice(bracketStart), results);
			break;
		}
		const parenStart = trimmed.indexOf("(", bracketEnd);
		if (parenStart === -1 || parenStart !== bracketEnd + 1) {
			addPlainAuthors(trimmed.slice(bracketStart, bracketEnd + 1), results);
			cursor = bracketEnd + 1;
			continue;
		}
		const parenEnd = trimmed.indexOf(")", parenStart);
		if (parenEnd === -1) {
			addPlainAuthors(trimmed.slice(bracketStart), results);
			break;
		}
		const namesPart = trimmed.slice(bracketStart + 1, bracketEnd).trim();
		const url = nonEmpty(unescapeUrl(trimmed.slice(parenStart + 1, parenEnd).trim()));
		const names = splitCommaSeparated(namesPart);
		for (const name of names) results.push({
			name,
			url
		});
		cursor = parenEnd + 1;
	}
	return results;
}
/**
* Split plain text on ` and `, `,`, `&` to extract author names.
*/
function addPlainAuthors(text, results) {
	const parts = text.split(/\band\b|,|&/).map((s) => s.trim()).filter((s) => s.length > 0 && s.toLowerCase() !== "others");
	for (const name of parts) results.push({
		name,
		url: void 0
	});
}
/**
* Validate that a sketch.properties file is a Processing sketch.
* Checks for at least one known Processing sketch key.
*/
function isProcessingSketchProperties(content) {
	const raw = parseProperties(content);
	return Object.keys(raw).some((k) => k === "main" || k === "mode" || k === "mode.id" || k.startsWith("manifest."));
}
const processingSketchPropertiesSource = defineSource({
	async discover(context) {
		return getMatches(context.options, ["sketch.properties"]);
	},
	key: "processingSketchProperties",
	async parse(input, context) {
		const content = await readFile(resolve(context.options.path, input), "utf8");
		if (!isProcessingSketchProperties(content)) return;
		return {
			data: parse(content),
			source: input
		};
	},
	phase: 1
});
//#endregion
export { processingSketchPropertiesSource };
