import { CONFIG_VALIDATION, REPOSITORY_CHANGED } from "../../../constants/error-messages.js";
import { setUserEnv } from "../../../util/env.js";
import { regEx } from "../../../util/regex.js";
import { getConfigFileNames } from "../../../config/app-strings.js";
import { logger } from "../../../logger/index.js";
import { add } from "../../../util/host-rules.js";
import { parseJson } from "../../../util/common.js";
import { ExternalHostError } from "../../../types/errors/external-host-error.js";
import { readLocalFile, readSystemFile } from "../../../util/fs/index.js";
import { clear } from "../../../util/http/queue.js";
import { clear as clear$1 } from "../../../util/http/throttle.js";
import { getCache } from "../../../util/cache/repository/index.js";
import { maskToken } from "../../../util/mask.js";
import { setNpmrc } from "../../../modules/datasource/npm/npmrc.js";
import "../../../modules/datasource/npm/index.js";
import { scm } from "../../../modules/platform/scm.js";
import { mergeChildConfig } from "../../../config/utils.js";
import { migrateConfig } from "../../../config/migration.js";
import { platform } from "../../../modules/platform/index.js";
import { resolveConfigPresets } from "../../../config/presets/index.js";
import { validateConfig } from "../../../config/validation.js";
import { decryptConfig } from "../../../config/decrypt.js";
import { applySecretsAndVariablesToConfig } from "../../../config/secrets.js";
import "../../../config/index.js";
import { migrateAndValidate } from "../../../config/migrate-validate.js";
import { parseFileConfig } from "../../../config/parse.js";
import { getOnboardingConfig } from "../onboarding/branch/config.js";
import { getOnboardingConfigFromCache, getOnboardingFileNameFromCache, setOnboardingConfigDetails } from "../onboarding/branch/onboarding-branch-cache.js";
import { OnboardingState, getDefaultConfigFileName } from "../onboarding/common.js";
import { isNonEmptyArray, isNonEmptyObject, isNonEmptyString, isString } from "@sindresorhus/is";
//#region lib/workers/repository/init/merge.ts
async function detectConfigFile() {
	const fileList = await scm.getFileList();
	for (const fileName of getConfigFileNames()) if (fileName === "package.json") try {
		if (JSON.parse(await readLocalFile("package.json", "utf8")).renovate) {
			logger.warn("Using package.json for Renovate config is deprecated - please use a dedicated configuration file instead");
			return "package.json";
		}
	} catch {}
	else if (fileList.includes(fileName)) return fileName;
	return null;
}
async function detectRepoFileConfig(branchName) {
	const cache = getCache();
	let { configFileName } = cache;
	if (isNonEmptyString(configFileName)) {
		let configFileRaw;
		try {
			configFileRaw = await platform.getRawFile(configFileName, void 0, branchName);
		} catch (err) {
			// istanbul ignore if
			if (err instanceof ExternalHostError) throw err;
			configFileRaw = null;
		}
		if (configFileRaw) {
			let configFileParsed = parseJson(configFileRaw, configFileName);
			if (configFileName === "package.json") configFileParsed = configFileParsed.renovate;
			return {
				configFileName,
				configFileParsed
			};
		} else {
			logger.debug("Existing config file no longer exists");
			delete cache.configFileName;
		}
	}
	if (OnboardingState.onboardingCacheValid) configFileName = getOnboardingFileNameFromCache();
	else configFileName = await detectConfigFile() ?? void 0;
	if (!configFileName) {
		logger.debug("No renovate config file found");
		cache.configFileName = "";
		return {};
	}
	cache.configFileName = configFileName;
	logger.debug(`Found ${configFileName} config file`);
	let configFileParsed;
	let configFileRaw;
	if (OnboardingState.onboardingCacheValid) {
		const cachedConfig = getOnboardingConfigFromCache();
		const parsedConfig = cachedConfig ? JSON.parse(cachedConfig) : void 0;
		if (parsedConfig) {
			setOnboardingConfigDetails(configFileName, JSON.stringify(parsedConfig));
			return {
				configFileName,
				configFileParsed: parsedConfig
			};
		}
	}
	if (configFileName === "package.json") {
		configFileParsed = JSON.parse(await readLocalFile("package.json", "utf8")).renovate;
		if (isString(configFileParsed)) {
			logger.debug("Massaging string renovate config to extends array");
			configFileParsed = { extends: [configFileParsed] };
		}
		logger.debug({ config: configFileParsed }, "package.json>renovate config");
	} else {
		configFileRaw = await readLocalFile(configFileName, "utf8");
		// istanbul ignore if
		if (!isString(configFileRaw)) {
			logger.warn({ configFileName }, "Null contents when reading config file");
			throw new Error(REPOSITORY_CHANGED);
		}
		// istanbul ignore if
		if (!configFileRaw.length) configFileRaw = "{}";
		const parseResult = parseFileConfig(configFileName, configFileRaw);
		if (!parseResult.success) return {
			configFileName,
			configFileParseError: {
				validationError: parseResult.validationError,
				validationMessage: parseResult.validationMessage
			}
		};
		configFileParsed = parseResult.parsedContents;
		logger.debug({
			fileName: configFileName,
			config: configFileParsed
		}, "Repository config");
	}
	setOnboardingConfigDetails(configFileName, JSON.stringify(configFileParsed));
	return {
		configFileName,
		configFileParsed
	};
}
function checkForRepoConfigError(repoConfig) {
	if (!repoConfig.configFileParseError) return;
	const error = new Error(CONFIG_VALIDATION);
	error.validationSource = repoConfig.configFileName;
	error.validationError = repoConfig.configFileParseError.validationError;
	error.validationMessage = repoConfig.configFileParseError.validationMessage;
	throw error;
}
async function mergeRenovateConfig(config, branchName) {
	let returnConfig = { ...config };
	let repoConfig = {};
	if (config.requireConfig !== "ignored") repoConfig = await detectRepoFileConfig(branchName);
	if (!repoConfig.configFileParsed && config.mode === "silent") {
		logger.debug("When mode=silent and repo has no config file, we use the onboarding config as repo config");
		repoConfig = {
			configFileName: getDefaultConfigFileName(),
			configFileParsed: await getOnboardingConfig(config)
		};
	}
	const resolvedRepoConfig = await resolveStaticRepoConfig(repoConfig?.configFileParsed ?? {}, process.env.RENOVATE_X_STATIC_REPO_CONFIG_FILE);
	if (isNonEmptyArray(returnConfig.extends)) {
		resolvedRepoConfig.extends = [...returnConfig.extends, ...resolvedRepoConfig.extends ?? []];
		delete returnConfig.extends;
	}
	checkForRepoConfigError(repoConfig);
	const migratedConfig = await migrateAndValidate(config, resolvedRepoConfig);
	if (migratedConfig.errors?.length) {
		const error = new Error(CONFIG_VALIDATION);
		error.validationSource = repoConfig.configFileName;
		error.validationError = "The renovate configuration file contains some invalid settings";
		error.validationMessage = migratedConfig.errors.map((e) => e.message).join(", ");
		throw error;
	}
	if (migratedConfig.warnings) returnConfig.warnings = [...returnConfig.warnings ?? [], ...migratedConfig.warnings];
	delete migratedConfig.errors;
	delete migratedConfig.warnings;
	const repository = config.repository;
	const decryptedConfig = await decryptConfig(migratedConfig, repository);
	applyNpmrc(decryptedConfig, "decrypted");
	const { config: configToDecrypt } = await resolveConfigPresets(decryptedConfig, config, config.ignorePresets);
	let resolvedConfig = await decryptConfig(configToDecrypt, repository);
	logger.trace({ config: resolvedConfig }, "resolved config");
	const migrationResult = migrateConfig(resolvedConfig);
	if (migrationResult.isMigrated) {
		logger.debug("Resolved config needs migrating");
		logger.trace({ config: resolvedConfig }, "resolved config after migrating");
		resolvedConfig = migrationResult.migratedConfig;
	}
	if (isString(resolvedConfig.npmrc)) logger.debug("Ignoring any .npmrc files in repository due to configured npmrc");
	applyNpmrc(resolvedConfig, "resolved");
	resolvedConfig = applySecretsAndVariablesToConfig({
		config: resolvedConfig,
		secrets: mergeChildConfig(config.secrets ?? {}, resolvedConfig.secrets ?? {}),
		variables: mergeChildConfig(config.variables ?? {}, resolvedConfig.variables ?? {})
	});
	applyHostRules(resolvedConfig);
	returnConfig = mergeChildConfig(returnConfig, resolvedConfig);
	({config: returnConfig} = await resolveConfigPresets(returnConfig, config));
	returnConfig.renovateJsonPresent = true;
	// istanbul ignore if
	if (returnConfig.ignorePaths?.length) logger.debug({ ignorePaths: returnConfig.ignorePaths }, `Found repo ignorePaths`);
	setUserEnv(returnConfig.env);
	delete returnConfig.env;
	return returnConfig;
}
function applyNpmrc(config, configType) {
	setNpmTokenInNpmrc(config);
	if (!isString(config.npmrc)) return;
	logger.debug(`Setting npmrc from ${configType ? `${configType} ` : ""}config`);
	setNpmrc(config.npmrc);
}
function applyHostRules(config) {
	if (!config.hostRules) return;
	logger.debug("Setting hostRules from config");
	for (const rule of config.hostRules) try {
		add(rule);
	} catch (err) {
		logger.warn({
			err,
			config: rule
		}, "Error setting hostRule from config");
	}
	clear();
	clear$1();
	delete config.hostRules;
}
/** needed when using portal secrets for npmToken */
function setNpmTokenInNpmrc(config) {
	if (!isString(config.npmToken)) return;
	const token = config.npmToken;
	logger.debug({ npmToken: maskToken(token) }, "Migrating npmToken to npmrc");
	if (!isString(config.npmrc)) {
		logger.debug("Adding npmrc to config");
		config.npmrc = `//registry.npmjs.org/:_authToken=${token}\n`;
		delete config.npmToken;
		return;
	}
	if (config.npmrc.includes(`\${NPM_TOKEN}`)) {
		logger.debug(`Replacing \${NPM_TOKEN} with npmToken`);
		config.npmrc = config.npmrc.replace(regEx(/\${NPM_TOKEN}/g), token);
	} else {
		logger.debug("Appending _authToken= to end of existing npmrc");
		config.npmrc = config.npmrc.replace(regEx(/\n?$/), `\n_authToken=${token}\n`);
	}
	delete config.npmToken;
}
async function resolveStaticRepoConfig(config, filename) {
	if (!isNonEmptyString(filename)) return config;
	let staticRepoConfig;
	try {
		staticRepoConfig = await tryReadStaticRepoFileConfig(filename);
	} catch (err) {
		logger.fatal({ err }, "Failed to load static repository config file");
		process.exit(1);
	}
	if (!isNonEmptyObject(staticRepoConfig)) return config;
	return mergeStaticConfig(config, staticRepoConfig);
}
async function tryReadStaticRepoFileConfig(staticRepoConfigFile) {
	logger.debug(`Reading static repo config file from ${staticRepoConfigFile}`);
	let staticRepoConfigRaw;
	try {
		staticRepoConfigRaw = await readSystemFile(staticRepoConfigFile, "utf8");
	} catch (err) {
		throw new Error(`Failed to read static repo config file: "${staticRepoConfigFile}"`, { cause: err });
	}
	const staticRepoConfig = parseJson(staticRepoConfigRaw, staticRepoConfigFile);
	const { errors, warnings } = await validateConfig("repo", staticRepoConfig);
	if (isNonEmptyArray(errors) || isNonEmptyArray(warnings)) logger.info({
		errors,
		warnings
	}, "Static repo config validation issues detected");
	else logger.debug({ staticRepoConfig }, "Static repository config file successfully parsed and validated");
	return staticRepoConfig;
}
function mergeStaticConfig(config, staticRepoConfig) {
	if (isNonEmptyArray(staticRepoConfig.extends)) {
		config.extends = [...staticRepoConfig.extends, ...config.extends ?? []];
		delete staticRepoConfig.extends;
	}
	return mergeChildConfig(staticRepoConfig, config);
}
//#endregion
export { applyHostRules, detectConfigFile, detectRepoFileConfig, mergeRenovateConfig };

//# sourceMappingURL=merge.js.map