import { getMatches } from "../file-matching.js";
import { defineSource } from "../source.js";
import { nonEmptyString, optionalUrl, stringArray } from "../utilities/schema-primitives.js";
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";
import is from "@sindresorhus/is";
import { z } from "zod";
import plist from "plist";
//#region src/lib/sources/xcode-info-plist.ts
/**
* Source and parser for Apple `Info.plist` files.
*
* Handles three flavors of Info.plist:
*   - Standard Apple app/framework bundles (CFBundle* keys)
*   - Alfred workflow plists (name, createdby, bundleid, etc.)
*   - TextMate bundle plists (name, contactName, contactEmailRot13, etc.)
*
* Uses the `plist` npm package to parse XML plist format.
*/
const infoPlistSchema = z.object({
	applicationCategory: nonEmptyString,
	author: nonEmptyString,
	authorEmail: nonEmptyString,
	copyrightHolder: nonEmptyString,
	copyrightYear: nonEmptyString,
	description: nonEmptyString,
	identifier: nonEmptyString,
	name: nonEmptyString,
	operatingSystems: stringArray,
	processorRequirements: stringArray,
	url: optionalUrl,
	version: nonEmptyString
});
/** Xcode build variable pattern: $(VAR) or ${VAR}. */
const XCODE_VARIABLE_RE = /\$[({][^)}]+[)}]/;
/**
* Map DTPlatformName / CFBundleSupportedPlatforms values to human-readable OS names.
*/
const PLATFORM_NAME_MAP = {
	appletvos: "tvOS",
	appletvsimulator: "tvOS",
	iphoneos: "iOS",
	iphonesimulator: "iOS",
	macosx: "macOS",
	watchos: "watchOS",
	watchsimulator: "watchOS",
	xros: "visionOS"
};
/**
* Parse an `Info.plist` content string into a structured object.
* Returns undefined if the plist is malformed or not a dictionary.
*/
function parse(content) {
	let data;
	try {
		const parsed = plist.parse(content);
		if (!is.plainObject(parsed)) return;
		data = parsed;
	} catch {
		return;
	}
	const name = getString(data, "CFBundleDisplayName") ?? getString(data, "CFBundleName") ?? getString(data, "name");
	const description = getString(data, "description");
	const version = getString(data, "CFBundleShortVersionString") ?? getString(data, "version") ?? getString(data, "CFBundleVersion");
	const identifier = getString(data, "CFBundleIdentifier") ?? getString(data, "bundleid");
	const { copyrightHolder, copyrightYear } = parseCopyright(data);
	const authorName = getString(data, "createdby") ?? getString(data, "contactName");
	const authorEmailRot13 = getString(data, "contactEmailRot13");
	const authorEmail = authorEmailRot13 ? rot13(authorEmailRot13) : void 0;
	const url = getString(data, "webaddress");
	const applicationCategory = parseApplicationCategory(data);
	return infoPlistSchema.parse({
		applicationCategory,
		author: authorName,
		authorEmail,
		copyrightHolder,
		copyrightYear,
		description,
		identifier,
		name,
		operatingSystems: parseOperatingSystems(data),
		processorRequirements: parseProcessorRequirements(data),
		url,
		version
	});
}
/**
* Get a string value from a plist dict, returning undefined for
* empty strings, whitespace-only strings, and Xcode variable placeholders.
*/
function getString(data, key) {
	const value = data[key];
	if (typeof value !== "string") return void 0;
	const trimmed = value.trim();
	if (trimmed.length === 0) return void 0;
	if (XCODE_VARIABLE_RE.test(trimmed)) return void 0;
	return trimmed;
}
/**
* ROT13-decode a string (used by TextMate bundles for obfuscated emails).
*/
function rot13(value) {
	return value.replaceAll(/[a-z]/gi, (c) => {
		const base = c <= "Z" ? 65 : 97;
		return String.fromCodePoint((c.codePointAt(0) - base + 13) % 26 + base);
	});
}
/**
* Convert an Apple UTI application category to a human-readable string.
* e.g. "public.app-category.developer-tools" → "Developer Tools"
*/
function humanizeCategory(uti) {
	if (!uti.startsWith("public.app-category.")) return uti;
	return uti.slice(20).split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
}
/**
* Parse application category from LSApplicationCategoryType.
*/
function parseApplicationCategory(data) {
	const category = getString(data, "LSApplicationCategoryType");
	if (!category) return void 0;
	return humanizeCategory(category);
}
/**
* Parse copyright information from NSHumanReadableCopyright or CFBundleGetInfoString.
*/
function parseCopyright(data) {
	const copyrightSource = getString(data, "NSHumanReadableCopyright") ?? getString(data, "CFBundleGetInfoString");
	if (!copyrightSource) return {};
	const copyrightYear = /(?:©|\(c\)|copyright)\s*(\d{4})/i.exec(copyrightSource)?.[1];
	const holderMatch = /(?:©|\(c\)|copyright)\s*\d{4}\s*(.+)/i.exec(copyrightSource);
	let copyrightHolder;
	if (holderMatch) {
		copyrightHolder = holderMatch[1].replace(/\.\s*all\s+rights\s+reserved\.?/i, "").replace(/[.,;]+$/, "").trim();
		if (copyrightHolder.length === 0) copyrightHolder = void 0;
	}
	return {
		copyrightHolder,
		copyrightYear
	};
}
/**
* Infer operating systems from various plist keys.
*/
function parseOperatingSystems(data) {
	const results = [];
	const seen = /* @__PURE__ */ new Set();
	function add(value) {
		if (!seen.has(value)) {
			seen.add(value);
			results.push(value);
		}
	}
	const minMacOS = getString(data, "LSMinimumSystemVersion");
	if (minMacOS) add(`macOS >= ${minMacOS}`);
	const minIOS = getString(data, "MinimumOSVersion");
	if (minIOS) add(`iOS >= ${minIOS}`);
	if (results.length > 0) return results;
	const platformName = getString(data, "DTPlatformName");
	if (platformName) {
		const os = PLATFORM_NAME_MAP[platformName.toLowerCase()];
		if (os) add(os);
	}
	const supportedPlatforms = data.CFBundleSupportedPlatforms;
	if (Array.isArray(supportedPlatforms)) {
		for (const p of supportedPlatforms) if (typeof p === "string") {
			const os = PLATFORM_NAME_MAP[p.toLowerCase()];
			if (os) add(os);
		}
	}
	if (data.LSRequiresIPhoneOS === true && !seen.has("iOS")) add("iOS");
	return results;
}
/**
* Extract processor architecture requirements from UIRequiredDeviceCapabilities.
*/
function parseProcessorRequirements(data) {
	const capabilities = data.UIRequiredDeviceCapabilities;
	if (!Array.isArray(capabilities)) return [];
	const results = [];
	for (const c of capabilities) if (typeof c === "string" && /^(?:arm|x86|i386)/i.test(c)) results.push(c);
	return results;
}
const xcodeInfoPlistSource = defineSource({
	async discover(context) {
		return getMatches(context.options, ["Info.plist"]);
	},
	key: "xcodeInfoPlist",
	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 { xcodeInfoPlistSource };
