import { logger } from "../../../../../../logger/index.js";
import api from "../../../../../versioning/npm/index.js";
import { updateDependency } from "../../dependency/index.js";
import { findFirstParentVersion } from "../common/parent-version.js";
import { findDepConstraints } from "./dep-constraints.js";
import { getLockedDependencies } from "./get-locked.js";
import detectIndent from "detect-indent";

//#region lib/modules/manager/npm/update/locked-dependency/package-lock/index.ts
async function updateLockedDependency(config, isParentUpdate = false) {
	const { depName, currentVersion, newVersion, packageFile, packageFileContent, lockFile, lockFileContent, allowParentUpdates = true, allowHigherOrRemoved = false } = config;
	logger.debug(`npm.updateLockedDependency: ${depName}@${currentVersion} -> ${newVersion} [${lockFile}]`);
	try {
		let packageJson;
		let packageLockJson;
		const detectedIndent = detectIndent(lockFileContent).indent || "  ";
		let newPackageJsonContent;
		try {
			packageJson = JSON.parse(packageFileContent);
			packageLockJson = JSON.parse(lockFileContent);
		} catch (err) {
			logger.warn({ err }, "Failed to parse files");
			return { status: "update-failed" };
		}
		const { lockfileVersion } = packageLockJson;
		const lockedDeps = getLockedDependencies(packageLockJson, depName, currentVersion);
		if (lockedDeps.some((dep) => dep.bundled)) {
			logger.debug(`Package ${depName}@${currentVersion} is bundled and cannot be updated`);
			return { status: "update-failed" };
		}
		if (!lockedDeps.length) {
			const newLockedDeps = getLockedDependencies(packageLockJson, depName, newVersion);
			let status;
			if (newLockedDeps.length) {
				logger.debug(`${depName}@${currentVersion} not found in ${lockFile} but ${depName}@${newVersion} was - looks like it's already updated`);
				status = "already-updated";
			} else if (lockfileVersion !== 1) {
				logger.debug(`Found lockfileVersion ${packageLockJson.lockfileVersion}`);
				status = "update-failed";
			} else if (allowHigherOrRemoved) {
				const anyVersionLocked = getLockedDependencies(packageLockJson, depName, null);
				if (anyVersionLocked.length) if (anyVersionLocked.every((dep) => api.isGreaterThan(dep.version, newVersion))) {
					logger.debug(`${depName} found in ${lockFile} with higher version - looks like it's already updated`);
					status = "already-updated";
				} else {
					logger.debug({ anyVersionLocked }, `Found alternative versions of qs`);
					status = "update-failed";
				}
				else {
					logger.debug(`${depName} not found in ${lockFile} - looks like it's already removed`);
					status = "already-updated";
				}
			} else {
				logger.debug(`${depName}@${currentVersion} not found in ${lockFile} - cannot update`);
				status = "update-failed";
			}
			/* v8 ignore next -- too hard to replicate */
			if (isParentUpdate) {
				const files = {};
				files[packageFile] = packageFileContent;
				files[lockFile] = lockFileContent;
				return {
					status,
					files
				};
			}
			return { status };
		}
		logger.debug(`Found matching dependencies with length ${lockedDeps.length}`);
		const constraints = findDepConstraints(packageJson, packageLockJson, depName, currentVersion, newVersion);
		logger.trace({
			deps: lockedDeps,
			constraints
		}, "Matching details");
		if (!constraints.length) {
			logger.info({
				depName,
				currentVersion,
				newVersion
			}, "Could not find constraints for the locked dependency - cannot remediate");
			return { status: "update-failed" };
		}
		const parentUpdates = [];
		for (const { parentDepName, parentVersion, constraint, depType } of constraints) if (api.matches(newVersion, constraint)) logger.debug(`${depName} can be updated to ${newVersion} in-range with matching constraint "${constraint}" in ${parentDepName ? `${parentDepName}@${parentVersion}` : packageFile}`);
		else if (parentDepName && parentVersion) {
			if (!allowParentUpdates) {
				logger.debug(`Cannot update ${depName} to ${newVersion} without an update to ${parentDepName}`);
				return { status: "update-failed" };
			}
			const parentNewVersion = await findFirstParentVersion(parentDepName, parentVersion, depName, newVersion);
			if (parentNewVersion) if (parentNewVersion === parentVersion) logger.debug(`Update of ${depName} to ${newVersion} already achieved in parent ${parentDepName}@${parentNewVersion}`);
			else {
				logger.debug(`Update of ${depName} to ${newVersion} can be achieved due to parent ${parentDepName}`);
				const parentUpdate = {
					depName: parentDepName,
					currentVersion: parentVersion,
					newVersion: parentNewVersion
				};
				parentUpdates.push(parentUpdate);
			}
			else {
				logger.debug(`Update of ${depName} to ${newVersion} cannot be achieved due to parent ${parentDepName}`);
				return { status: "update-failed" };
			}
		} else if (depType) newPackageJsonContent = updateDependency({
			fileContent: packageFileContent,
			upgrade: {
				depName,
				depType,
				newValue: api.getNewValue({
					currentValue: constraint,
					rangeStrategy: "replace",
					currentVersion,
					newVersion
				})
			}
		});
		for (const dependency of lockedDeps) {
			dependency.version = newVersion;
			delete dependency.resolved;
			delete dependency.integrity;
		}
		let newLockFileContent = JSON.stringify(packageLockJson, null, detectedIndent);
		for (const parentUpdate of parentUpdates) {
			const parentUpdateResult = await updateLockedDependency({
				...config,
				...parentUpdate,
				lockFileContent: newLockFileContent,
				packageFileContent: newPackageJsonContent ?? packageFileContent
			}, true);
			/* v8 ignore next -- hard to test due to recursion */
			if (!parentUpdateResult.files) {
				logger.debug(`Update of ${depName} to ${newVersion} impossible due to failed update of parent ${parentUpdate.depName} to ${parentUpdate.newVersion}`);
				return { status: "update-failed" };
			}
			newPackageJsonContent = parentUpdateResult.files[packageFile] || newPackageJsonContent;
			/* v8 ignore next 2 -- hard to test */
			newLockFileContent = parentUpdateResult.files[lockFile] || newLockFileContent;
		}
		const files = {};
		if (newLockFileContent) files[lockFile] = newLockFileContent;
		if (newPackageJsonContent) files[packageFile] = newPackageJsonContent;
		else if (lockfileVersion !== 1) {
			logger.debug("Remediations which change package-lock.json only are not supported unless lockfileVersion=1");
			return { status: "unsupported" };
		}
		return {
			status: "updated",
			files
		};
	} catch (err) {
		logger.error({ err }, "updateLockedDependency() error");
		return { status: "update-failed" };
	}
}

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