import { log } from "./log.js";
import { getTree, resetMatchCache } from "./file-matching.js";
import { DEFAULT_GET_METADATA_OPTIONS } from "./metadata-types.js";
import { stripUndefined } from "./utilities/template-helpers.js";
import { arduinoLibraryPropertiesSource } from "./sources/arduino-library-properties.js";
import { cinderCinderblockXmlSource } from "./sources/cinder-cinderblock-xml.js";
import { codeStatsSource } from "./sources/code-stats.js";
import { codemetaJsonSource } from "./sources/codemeta-json.js";
import { dependencyUpdatesSource } from "./sources/dependency-updates.js";
import { fileStatsSource } from "./sources/file-stats.js";
import { gitConfigSource } from "./sources/git-config.js";
import { gitStatsSource } from "./sources/git-stats.js";
import { githubSource } from "./sources/github.js";
import { githubActionsSource } from "./sources/github-actions.js";
import { goGoModSource } from "./sources/go-go-mod.js";
import { goGoreleaserYamlSource } from "./sources/go-goreleaser-yaml.js";
import { javaPomXmlSource } from "./sources/java-pom-xml.js";
import { licenseFileSource } from "./sources/license-file.js";
import { metadataFileSource } from "./sources/metadata-file.js";
import { metascopeSource } from "./sources/metascope.js";
import { nodePackageJsonSource } from "./sources/node-package-json.js";
import { nodeNpmRegistrySource } from "./sources/node-npm-registry.js";
import { obsidianPluginManifestJsonSource } from "./sources/obsidian-plugin-manifest-json.js";
import { obsidianPluginRegistrySource } from "./sources/obsidian-plugin-registry.js";
import { openframeworksAddonConfigMkSource } from "./sources/openframeworks-addon-config-mk.js";
import { openframeworksInstallXmlSource } from "./sources/openframeworks-install-xml.js";
import { processingLibraryPropertiesSource } from "./sources/processing-library-properties.js";
import { processingSketchPropertiesSource } from "./sources/processing-sketch-properties.js";
import { publiccodeYamlSource } from "./sources/publiccode-yaml.js";
import { pythonPkgInfoSource } from "./sources/python-pkg-info.js";
import { pythonPyprojectTomlSource } from "./sources/python-pyproject-toml.js";
import { pythonSetupCfgSource } from "./sources/python-setup-cfg.js";
import { pythonSetupPySource } from "./sources/python-setup-py.js";
import { pythonPypiRegistrySource } from "./sources/python-pypi-registry.js";
import { readmeFileSource } from "./sources/readme-file.js";
import { rubyGemspecSource } from "./sources/ruby-gemspec.js";
import { rustCargoTomlSource } from "./sources/rust-cargo-toml.js";
import { xcodeInfoPlistSource } from "./sources/xcode-info-plist.js";
import { xcodeProjectPbxprojSource } from "./sources/xcode-project-pbxproj.js";
import { isKeyOfTemplate, templates } from "./templates/index.js";
import { defu } from "defu";
import { execFile } from "node:child_process";
import { stat } from "node:fs/promises";
import { resolve } from "node:path";
import { promisify } from "node:util";
import prettyMs from "pretty-ms";
//#region src/lib/metadata.ts
const execFileAsync = promisify(execFile);
/**
* All registered metadata sources.
* Each source declares its `phase` number. Sources with the same phase run in parallel.
* Lower phases run first, and their accumulated results are available to later phases
* via `context` in `SourceContext`.
*/
const sources = [
	arduinoLibraryPropertiesSource,
	cinderCinderblockXmlSource,
	codemetaJsonSource,
	gitConfigSource,
	githubActionsSource,
	goGoModSource,
	goGoreleaserYamlSource,
	javaPomXmlSource,
	licenseFileSource,
	metadataFileSource,
	metascopeSource,
	nodePackageJsonSource,
	obsidianPluginManifestJsonSource,
	openframeworksAddonConfigMkSource,
	openframeworksInstallXmlSource,
	processingLibraryPropertiesSource,
	processingSketchPropertiesSource,
	publiccodeYamlSource,
	pythonPkgInfoSource,
	pythonPyprojectTomlSource,
	pythonSetupCfgSource,
	pythonSetupPySource,
	readmeFileSource,
	rubyGemspecSource,
	rustCargoTomlSource,
	xcodeInfoPlistSource,
	xcodeProjectPbxprojSource,
	codeStatsSource,
	dependencyUpdatesSource,
	fileStatsSource,
	gitStatsSource,
	githubSource,
	nodeNpmRegistrySource,
	obsidianPluginRegistrySource,
	pythonPypiRegistrySource
];
/** All available source key names, for runtime use (e.g. CLI choices). */
const sourceNames = sources.map((s) => s.key);
/**
* Resolve GitHub token from multiple sources, in precedence order.
*/
async function resolveCredentials(credentials) {
	if (credentials?.githubToken) return credentials;
	const environmentToken = process.env.GITHUB_TOKEN;
	if (environmentToken) return {
		...credentials,
		githubToken: environmentToken
	};
	try {
		const { stdout } = await execFileAsync("gh", ["auth", "token"]);
		const token = stdout.trim();
		if (token) return {
			...credentials,
			githubToken: token
		};
	} catch {}
	return credentials ?? {};
}
/**
* Resolve a template option to a template function.
* Accepts a built-in template name (string) or a template function.
*/
function resolveTemplate(template) {
	if (template === void 0) return void 0;
	if (typeof template === "function") return template;
	if (isKeyOfTemplate(template)) return templates[template];
	log.warn(`Unknown template: "${template}". Using default (all fields).`);
}
/**
* Check availability and extract data from a set of sources in parallel,
* writing results into the provided MetadataContext.
*/
async function runSources(phaseSources, sourceContext, result) {
	const extractResults = await Promise.all(phaseSources.map(async (source) => {
		try {
			const startTime = performance.now();
			const data = await source.extract(sourceContext);
			const duration = Math.round(performance.now() - startTime);
			log.debug(`Source "${source.key}" extracted in ${duration}ms`);
			return {
				data,
				key: source.key
			};
		} catch (error) {
			log.warn(`Source "${source.key}" extraction failed: ${error instanceof Error ? error.message : String(error)}`);
			return {
				data: void 0,
				key: source.key
			};
		}
	}));
	for (const entry of extractResults) if (entry.data !== void 0) Object.assign(result, { [entry.key]: entry.data });
}
/**
* Extract metadata from a project directory.
*/
async function getMetadata(options) {
	const startTime = performance.now();
	const resolvedOptions = defu(options ?? {}, DEFAULT_GET_METADATA_OPTIONS);
	const absolutePath = resolve(resolvedOptions.path);
	let stats;
	try {
		stats = await stat(absolutePath);
	} catch {
		throw new Error(`Path does not exist: ${absolutePath}`);
	}
	if (!stats.isDirectory()) throw new Error(`Path is not a directory: ${absolutePath}`);
	const template = resolveTemplate(resolvedOptions.template);
	resetMatchCache();
	log.debug(`Building file tree (respectIgnored: ${resolvedOptions.respectIgnored})...`);
	const [credentials, rootTree] = await Promise.all([resolveCredentials(resolvedOptions.credentials), getTree(absolutePath, resolvedOptions.respectIgnored)]);
	log.debug(`Root file tree contains ${rootTree.length} entries`);
	const context = {
		arduinoLibraryProperties: void 0,
		cinderCinderblockXml: void 0,
		codemetaJson: void 0,
		codeStats: void 0,
		dependencyUpdates: void 0,
		fileStats: void 0,
		gitConfig: void 0,
		github: void 0,
		githubActions: void 0,
		gitStats: void 0,
		goGoMod: void 0,
		goGoreleaserYaml: void 0,
		javaPomXml: void 0,
		licenseFile: void 0,
		metadataFile: void 0,
		metascope: void 0,
		nodeNpmRegistry: void 0,
		nodePackageJson: void 0,
		obsidianPluginManifestJson: void 0,
		obsidianPluginRegistry: void 0,
		openframeworksAddonConfigMk: void 0,
		openframeworksInstallXml: void 0,
		processingLibraryProperties: void 0,
		processingSketchProperties: void 0,
		publiccodeYaml: void 0,
		pythonPkgInfo: void 0,
		pythonPypiRegistry: void 0,
		pythonPyprojectToml: void 0,
		pythonSetupCfg: void 0,
		pythonSetupPy: void 0,
		readmeFile: void 0,
		rubyGemspec: void 0,
		rustCargoToml: void 0,
		xcodeInfoPlist: void 0,
		xcodeProjectPbxproj: void 0
	};
	const activeSources = resolvedOptions.sources ? sources.filter((s) => resolvedOptions.sources.includes(s.key)) : sources;
	const completedSources = /* @__PURE__ */ new Set();
	const phases = new Set(activeSources.map((s) => s.phase));
	for (const phase of [...phases].toSorted((a, b) => a - b)) {
		const phaseSources = activeSources.filter((s) => s.phase === phase);
		log.debug(`Phase ${phase}: Running ${phaseSources.length} sources...`);
		await runSources(phaseSources, {
			completedSources,
			metadata: { ...context },
			options: {
				...resolvedOptions,
				credentials,
				path: absolutePath
			}
		}, context);
		for (const source of phaseSources) completedSources.add(source.key);
	}
	const metadataDuration = performance.now() - startTime;
	if (context.metascope) context.metascope.data.durationMs = Math.round(metadataDuration);
	log.debug(`Metadata duration: ${prettyMs(metadataDuration)}`);
	if (template) {
		const templateStartTime = performance.now();
		const finalTemplateResult = stripUndefined(template(context, resolvedOptions.templateData ?? {})) ?? {};
		const templateDuration = performance.now() - templateStartTime;
		log.debug(`Template duration: ${prettyMs(templateDuration)}`);
		log.debug(`Total duration: ${prettyMs(performance.now() - startTime)}`);
		return finalTemplateResult;
	}
	const finalResult = stripUndefined(context);
	log.debug(`Total duration: ${prettyMs(performance.now() - startTime)}`);
	return finalResult;
}
//#endregion
export { getMetadata, sourceNames };
