import { getMatches } from "../file-matching.js";
import { defineSource } from "../source.js";
import { nonEmptyString, stringArray } from "../utilities/schema-primitives.js";
import { resolve } from "node:path";
import is from "@sindresorhus/is";
import { z } from "zod";
import { PBXNativeTarget, XCRemoteSwiftPackageReference, XcodeProject } from "@bacons/xcode";
//#region src/lib/sources/xcode-project-pbxproj.ts
/**
* Source and parser for Xcode `.pbxproj` project files.
*
* Uses the `@bacons/xcode` package to parse the binary/ASCII plist format
* and navigate the Xcode project object graph.
*
* Extracts metadata from:
*   - Project-level attributes (organizationName)
*   - Build settings on the main app target (Release config preferred)
*   - Swift Package Manager dependencies
*/
const pbxprojDependencySchema = z.object({ url: z.string() });
const pbxprojSchema = z.object({
	copyrightHolder: nonEmptyString,
	copyrightYear: nonEmptyString,
	dependencies: z.array(pbxprojDependencySchema),
	identifier: nonEmptyString,
	name: nonEmptyString,
	operatingSystems: stringArray,
	organizationName: nonEmptyString,
	programmingLanguage: nonEmptyString,
	version: nonEmptyString
});
/** Xcode build variable pattern: $(VAR) or ${VAR}. */
const XCODE_VARIABLE_RE = /\$[({][^)}]+[)}]/;
/**
* Map SDKROOT values to human-readable OS names.
*/
const SDKROOT_MAP = {
	appletvos: "tvOS",
	iphoneos: "iOS",
	macosx: "macOS",
	watchos: "watchOS",
	xros: "visionOS"
};
/**
* Deployment target build setting keys and their OS label.
*/
const DEPLOYMENT_TARGETS = [
	{
		key: "IPHONEOS_DEPLOYMENT_TARGET",
		os: "iOS"
	},
	{
		key: "MACOSX_DEPLOYMENT_TARGET",
		os: "macOS"
	},
	{
		key: "TVOS_DEPLOYMENT_TARGET",
		os: "tvOS"
	},
	{
		key: "WATCHOS_DEPLOYMENT_TARGET",
		os: "watchOS"
	},
	{
		key: "XROS_DEPLOYMENT_TARGET",
		os: "visionOS"
	}
];
/**
* Parse a `.pbxproj` file into a structured object.
* Returns undefined if the file is malformed or cannot be parsed.
*/
function parse(filePath) {
	let project;
	try {
		project = XcodeProject.open(filePath);
	} catch {
		return;
	}
	const root = project.rootObject;
	const target = root.props.targets.find((t) => PBXNativeTarget.is(t));
	let appTarget;
	try {
		appTarget = root.getMainAppTarget() ?? void 0;
	} catch {}
	appTarget ??= target;
	let targetConfig;
	let targetSettings;
	if (appTarget) try {
		const config = appTarget.getDefaultConfiguration();
		targetConfig = config;
		const bs = config.props.buildSettings;
		targetSettings = is.plainObject(bs) ? bs : void 0;
	} catch {}
	let projectSettings;
	try {
		const pbs = (root.props.buildConfigurationList.props.buildConfigurations.find((c) => c.props.name === "Release") ?? root.props.buildConfigurationList.getDefaultConfiguration()).props.buildSettings;
		projectSettings = is.plainObject(pbs) ? pbs : void 0;
	} catch {}
	const displayName = getSetting(targetSettings, projectSettings, "INFOPLIST_KEY_CFBundleDisplayName");
	const productName = getResolvedSetting(targetConfig, targetSettings, projectSettings, "PRODUCT_NAME");
	const targetName = appTarget ? cleanString(appTarget.props.name) : void 0;
	const organizationName = cleanString(root.props.attributes.ORGANIZATIONNAME);
	const { copyrightHolder, copyrightYear } = parseCopyrightString(getSetting(targetSettings, projectSettings, "INFOPLIST_KEY_NSHumanReadableCopyright"));
	const swiftVersion = getSetting(targetSettings, projectSettings, "SWIFT_VERSION");
	let programmingLanguage;
	if (swiftVersion) programmingLanguage = "Swift";
	else if (getSetting(targetSettings, projectSettings, "GCC_C_LANGUAGE_STANDARD")) programmingLanguage = "Objective-C";
	return pbxprojSchema.parse({
		copyrightHolder,
		copyrightYear,
		dependencies: parseDependencies(root),
		identifier: getResolvedSetting(targetConfig, targetSettings, projectSettings, "PRODUCT_BUNDLE_IDENTIFIER"),
		name: displayName ?? productName ?? targetName,
		operatingSystems: parseOperatingSystems(targetSettings, projectSettings),
		organizationName,
		programmingLanguage,
		version: getSetting(targetSettings, projectSettings, "MARKETING_VERSION")
	});
}
/**
* Get a string build setting, filtering out empty strings and unresolved
* Xcode variable placeholders. Handles both string and numeric values.
*/
function cleanString(value) {
	if (typeof value === "number") return String(value);
	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;
}
/**
* Get a build setting from a target, cascading to project-level if not found.
*/
function getSetting(targetSettings, projectSettings, key) {
	const targetValue = cleanString(targetSettings?.[key]);
	if (targetValue !== void 0) return targetValue;
	return cleanString(projectSettings?.[key]);
}
/**
* Try to resolve a build setting using `@bacons/xcode`'s variable resolution,
* then fall back to raw value.
*/
function getResolvedSetting(config, targetSettings, projectSettings, key) {
	if (config) try {
		const cleaned = cleanString(config.resolveBuildSetting(key));
		if (cleaned !== void 0) return cleaned;
	} catch {}
	return getSetting(targetSettings, projectSettings, key);
}
/**
* Parse copyright information from a copyright string.
*/
function parseCopyrightString(copyrightSource) {
	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 deployment target build settings.
*/
function parseOperatingSystems(targetSettings, projectSettings) {
	const results = [];
	const seen = /* @__PURE__ */ new Set();
	for (const { key, os } of DEPLOYMENT_TARGETS) {
		const version = getSetting(targetSettings, projectSettings, key);
		if (version) {
			const value = `${os} >= ${version}`;
			if (!seen.has(value)) {
				seen.add(value);
				results.push(value);
			}
		}
	}
	if (results.length === 0) {
		const sdkroot = getSetting(targetSettings, projectSettings, "SDKROOT");
		if (sdkroot && sdkroot !== "auto") {
			const os = SDKROOT_MAP[sdkroot];
			if (os) results.push(os);
		}
	}
	return results;
}
/**
* Extract Swift Package Manager dependencies.
*/
function parseDependencies(root) {
	const packageReferences = root.props.packageReferences ?? [];
	const results = [];
	for (const packageReference of packageReferences) {
		if (!XCRemoteSwiftPackageReference.is(packageReference)) continue;
		const url = cleanString(packageReference.props.repositoryURL);
		if (url) results.push({ url });
	}
	return results;
}
const xcodeProjectPbxprojSource = defineSource({
	async discover(context) {
		return getMatches(context.options, ["*.xcodeproj/project.pbxproj"], ["**/*.xcodeproj/project.pbxproj"]);
	},
	key: "xcodeProjectPbxproj",
	async parse(input, context) {
		const data = parse(resolve(context.options.path, input));
		if (data !== void 0) return {
			data,
			source: input
		};
	},
	phase: 1
});
//#endregion
export { xcodeProjectPbxprojSource };
