import { CONFIG_VALIDATION, PLATFORM_RATE_LIMIT_EXCEEDED } from "../../constants/error-messages.js";
import { get, set } from "../../util/cache/memory/index.js";
import { regEx } from "../../util/regex.js";
import { GlobalConfig } from "../global.js";
import { logger } from "../../logger/index.js";
import { clone } from "../../util/clone.js";
import { compile } from "../../util/template/index.js";
import { ExternalHostError } from "../../types/errors/external-host-error.js";
import { get as get$1, set as set$1 } from "../../util/cache/package/index.js";
import { PRESET_DEP_NOT_FOUND, PRESET_INVALID, PRESET_INVALID_JSON, PRESET_NOT_FOUND, PRESET_PROHIBITED_SUBPRESET, PRESET_RENOVATE_CONFIG_NOT_FOUND } from "./util.js";
import { parsePreset } from "./parse.js";
import { massageConfig } from "../massage.js";
import { removedPresets } from "./common.js";
import { mergeChildConfig } from "../utils.js";
import { migrateConfig } from "../migration.js";
import { forgejo_exports } from "./forgejo/index.js";
import { gitea_exports } from "./gitea/index.js";
import { github_exports } from "./github/index.js";
import { gitlab_exports } from "./gitlab/index.js";
import { http_exports } from "./http/index.js";
import { getPreset as getPreset$1 } from "./internal/index.js";
import { local_exports } from "./local/index.js";
import { npm_exports } from "./npm/index.js";
import { isArray, isNullOrUndefined, isObject, isString } from "@sindresorhus/is";

//#region lib/config/presets/index.ts
const presetSources = {
	forgejo: forgejo_exports,
	gitea: gitea_exports,
	github: github_exports,
	gitlab: gitlab_exports,
	http: http_exports,
	local: local_exports,
	npm: npm_exports
};
const presetCacheNamespace = "preset";
function replaceArgs(obj, argMapping) {
	if (isString(obj)) {
		let returnStr = obj;
		for (const [arg, argVal] of Object.entries(argMapping)) {
			const re = regEx(`{{${arg}}}`, "g", false);
			returnStr = returnStr.replace(re, argVal);
		}
		return returnStr;
	}
	if (isArray(obj)) {
		const returnArray = [];
		for (const item of obj) returnArray.push(replaceArgs(item, argMapping));
		return returnArray;
	}
	if (isObject(obj)) {
		const returnObj = {};
		for (const [key, val] of Object.entries(obj)) returnObj[key] = replaceArgs(val, argMapping);
		return returnObj;
	}
	return obj;
}
async function getPreset(preset, baseConfig) {
	logger.trace(`getPreset(${preset})`);
	const newPreset = removedPresets[preset];
	if (newPreset) return getPreset(newPreset, baseConfig);
	if (newPreset === null) return {};
	const { presetSource, repo, presetPath, presetName, tag, params, rawParams } = parsePreset(preset);
	let presetConfig;
	if (presetSource === "internal") presetConfig = getPreset$1({
		repo,
		presetPath,
		presetName,
		tag
	});
	else {
		const cacheKey = `preset:${preset}`;
		const presetCachePersistence = GlobalConfig.get("presetCachePersistence", false);
		if (presetCachePersistence) presetConfig = await get$1(presetCacheNamespace, cacheKey);
		else presetConfig = get(cacheKey);
		if (isNullOrUndefined(presetConfig)) {
			presetConfig = await presetSources[presetSource].getPreset({
				repo,
				presetPath,
				presetName,
				tag
			});
			if (presetCachePersistence) await set$1(presetCacheNamespace, cacheKey, presetConfig, 15);
			else set(cacheKey, presetConfig);
		}
	}
	if (!presetConfig) throw new Error(PRESET_DEP_NOT_FOUND);
	logger.trace({ presetConfig }, `Found preset ${preset}`);
	if (params) {
		const argMapping = {};
		for (const [index, value] of params.entries()) argMapping[`arg${index}`] = value;
		if (rawParams) argMapping.args = rawParams;
		presetConfig = replaceArgs(presetConfig, argMapping);
	}
	logger.trace({ presetConfig }, `Applied params to preset ${preset}`);
	const presetKeys = Object.keys(presetConfig);
	if (presetKeys.length === 2 && presetKeys.includes("description") && presetKeys.includes("extends")) delete presetConfig.description;
	const packageListKeys = ["description", "matchPackageNames"];
	if (presetKeys.every((key) => packageListKeys.includes(key))) delete presetConfig.description;
	const { migratedConfig } = migrateConfig(presetConfig);
	return massageConfig(migratedConfig);
}
async function resolveConfigPresets(inputConfig, baseConfig, _ignorePresets, existingPresets = []) {
	const allVisitedPresets = { merged: /* @__PURE__ */ new Set() };
	let ignorePresets = clone(_ignorePresets);
	if (!ignorePresets || ignorePresets.length === 0) ignorePresets = inputConfig.ignorePresets ?? [];
	logger.trace({
		config: inputConfig,
		existingPresets
	}, "resolveConfigPresets");
	let config = {};
	if (inputConfig.extends?.length) {
		inputConfig.extends = inputConfig.extends.map((tmpl) => compile(tmpl, {}));
		for (const preset of inputConfig.extends) if (shouldResolvePreset(preset, existingPresets, ignorePresets)) {
			logger.trace(`Resolving preset "${preset}"`);
			const { config: presetConfig, visitedPresets } = await resolveConfigPresets(await fetchPreset(preset, baseConfig, inputConfig, existingPresets), baseConfig ?? inputConfig, ignorePresets, existingPresets.concat([preset]));
			if (inputConfig?.ignoreDeps?.length === 0) delete presetConfig.description;
			config = mergeChildConfig(config, presetConfig);
			allVisitedPresets.merged.add(preset);
			for (const mergedPreset of visitedPresets.merged) allVisitedPresets.merged.add(mergedPreset);
		}
	}
	logger.trace({ config }, `Post-preset resolve config`);
	config = mergeChildConfig(config, inputConfig);
	delete config.extends;
	delete config.ignorePresets;
	logger.trace({ config }, `Post-merge resolve config`);
	for (const [key, val] of Object.entries(config)) {
		const ignoredKeys = ["content", "onboardingConfig"];
		if (isArray(val)) {
			config[key] = [];
			for (const element of val) if (isObject(element)) {
				const { config: presetConfig, visitedPresets: visited } = await resolveConfigPresets(element, baseConfig, ignorePresets, existingPresets);
				config[key].push(presetConfig);
				for (const mergedPreset of visited.merged) allVisitedPresets.merged.add(mergedPreset);
			} else config[key].push(element);
		} else if (isObject(val) && !ignoredKeys.includes(key)) {
			logger.trace(`Resolving object "${key}"`);
			const { config: presetConfig, visitedPresets: visited } = await resolveConfigPresets(val, baseConfig, ignorePresets, existingPresets);
			config[key] = presetConfig;
			for (const mergedPreset of visited.merged) allVisitedPresets.merged.add(mergedPreset);
		}
	}
	logger.trace({ config: inputConfig }, "Input config");
	logger.trace({
		config,
		visitedPresets: allVisitedPresets
	}, "Resolved config");
	return {
		config,
		visitedPresets: { merged: Array.from(allVisitedPresets.merged) }
	};
}
async function fetchPreset(preset, baseConfig, inputConfig, existingPresets) {
	try {
		return await getPreset(preset, baseConfig ?? inputConfig);
	} catch (err) {
		logger.debug({
			preset,
			err
		}, "Preset fetch error");
		if (err instanceof ExternalHostError) throw err;
		if (err.message === PLATFORM_RATE_LIMIT_EXCEEDED) throw err;
		const error = new Error(CONFIG_VALIDATION);
		if (err.message === PRESET_DEP_NOT_FOUND) error.validationError = `Cannot find preset's package (${preset})`;
		else if (err.message === PRESET_RENOVATE_CONFIG_NOT_FOUND) error.validationError = `Preset package is missing a renovate-config entry (${preset})`;
		else if (err.message === PRESET_NOT_FOUND) error.validationError = `Preset name not found within published preset config (${preset})`;
		else if (err.message === PRESET_INVALID) error.validationError = `Preset is invalid (${preset})`;
		else if (err.message === PRESET_PROHIBITED_SUBPRESET) error.validationError = `Sub-presets cannot be combined with a custom path (${preset})`;
		else if (err.message === PRESET_INVALID_JSON) error.validationError = `Preset is invalid JSON (${preset})`;
		else error.validationError = `Preset caused unexpected error (${preset})`;
		if (existingPresets.length) error.validationError += ". Note: this is a *nested* preset so please contact the preset author if you are unable to fix it yourself.";
		logger.info({ validationError: error.validationError }, "Throwing preset error");
		throw error;
	}
}
function shouldResolvePreset(preset, existingPresets, ignorePresets) {
	if (existingPresets.includes(preset)) {
		logger.debug(`Already seen preset ${preset} in [${existingPresets.join(", ")}]`);
		return false;
	}
	if (ignorePresets.includes(preset)) {
		logger.debug(`Ignoring preset ${preset} in [${existingPresets.join(", ")}]`);
		return false;
	}
	return true;
}

//#endregion
export { resolveConfigPresets };
//# sourceMappingURL=index.js.map