import { newlineRegex, regEx } from "../../../util/regex.js";
import { logger } from "../../../logger/index.js";
import { DockerDatasource } from "../../datasource/docker/index.js";
import api, { id } from "../../versioning/debian/index.js";
import { id as id$1 } from "../../versioning/ubuntu/index.js";
import { isNonEmptyStringAndNotWhitespace, isString } from "@sindresorhus/is";
//#region lib/modules/manager/dockerfile/extract.ts
const variableMarker = "$";
function extractVariables(image) {
	const variables = {};
	const variableRegex = regEx(/(?<fullvariable>\\?\$(?<simplearg>\w+)|\\?\${(?<complexarg>\w+)(?::.+?)?}+)/gi);
	let match;
	do {
		match = variableRegex.exec(image);
		if (match?.groups?.fullvariable) variables[match.groups.fullvariable] = match.groups?.simplearg || match.groups?.complexarg;
	} while (match);
	return variables;
}
function getAutoReplaceTemplate(dep) {
	let template = dep.replaceString;
	if (dep.currentValue) {
		let placeholder = "{{#if newValue}}{{newValue}}{{/if}}";
		if (!dep.currentDigest) placeholder += "{{#if newDigest}}@{{newDigest}}{{/if}}";
		template = template?.replace(dep.currentValue, placeholder);
	}
	if (dep.currentDigest) template = template?.replace(dep.currentDigest, "{{#if newDigest}}{{newDigest}}{{/if}}");
	return template;
}
function processDepForAutoReplace(dep, lineNumberRanges, lines, linefeed) {
	const lineNumberRangesToReplace = [];
	for (const lineNumberRange of lineNumberRanges) for (const lineNumber of lineNumberRange) if (isString(dep.currentValue) && lines[lineNumber].includes(dep.currentValue) || isString(dep.currentDigest) && lines[lineNumber].includes(dep.currentDigest)) lineNumberRangesToReplace.push(lineNumberRange);
	lineNumberRangesToReplace.sort((a, b) => {
		return a[0] - b[0];
	});
	const minLine = lineNumberRangesToReplace[0]?.[0];
	const maxLine = lineNumberRangesToReplace[lineNumberRangesToReplace.length - 1]?.[1];
	if (lineNumberRanges.length === 1 || minLine === void 0 || maxLine === void 0) return;
	dep.replaceString = Array.from({ length: maxLine - minLine + 1 }, (_v, k) => k + minLine).map((lineNumber) => lines[lineNumber]).join(linefeed);
	if (!dep.currentDigest) dep.replaceString += linefeed;
	dep.autoReplaceStringTemplate = getAutoReplaceTemplate(dep);
}
function splitImageParts(currentFrom) {
	let isVariable = false;
	let cleanedCurrentFrom = currentFrom;
	if (cleanedCurrentFrom?.includes(variableMarker)) {
		const defaultValueMatch = regEx(/^\${.+?:-"?(?<value>.*?)"?}$/).exec(cleanedCurrentFrom)?.groups;
		if (defaultValueMatch?.value) {
			isVariable = true;
			cleanedCurrentFrom = defaultValueMatch.value;
		}
		if (cleanedCurrentFrom?.includes(variableMarker)) return { skipReason: "contains-variable" };
	}
	const [currentDepTag, currentDigest] = cleanedCurrentFrom.split("@");
	const depTagSplit = currentDepTag.split(":");
	let depName;
	let currentValue;
	if (depTagSplit.length === 1 || depTagSplit[depTagSplit.length - 1].includes("/")) depName = currentDepTag;
	else {
		currentValue = depTagSplit.pop();
		depName = depTagSplit.join(":");
	}
	const dep = {
		depName,
		packageName: depName,
		currentValue,
		currentDigest
	};
	if (isVariable) {
		dep.replaceString = cleanedCurrentFrom;
		if (!dep.currentValue) delete dep.currentValue;
		if (!dep.currentDigest) delete dep.currentDigest;
	}
	return dep;
}
const quayRegex = regEx(/^quay\.io(?::[1-9][0-9]{0,4})?/i);
function getDep(currentFrom, specifyReplaceString = true, registryAliases) {
	if (!isString(currentFrom) || !isNonEmptyStringAndNotWhitespace(currentFrom)) return { skipReason: "invalid-value" };
	for (const [name, value] of Object.entries(registryAliases ?? {})) if (currentFrom.startsWith(`${name}/`)) {
		const dep = {
			...getDep(`${value}/${currentFrom.substring(name.length + 1)}`, false),
			replaceString: currentFrom
		};
		if (dep.depName?.startsWith(value)) {
			dep.packageName = dep.depName;
			dep.depName = `${name}/${dep.depName.substring(value.length + 1)}`;
		}
		if (specifyReplaceString) dep.autoReplaceStringTemplate = getAutoReplaceTemplate(dep);
		return dep;
	}
	const dep = splitImageParts(currentFrom);
	if (specifyReplaceString) {
		dep.replaceString ??= currentFrom;
		dep.autoReplaceStringTemplate = "{{depName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}";
	}
	dep.datasource = DockerDatasource.id;
	if (dep.depName) {
		for (const prefix of [
			"amd64",
			"arm64",
			"library"
		]) if (dep.depName.startsWith(`${prefix}/`)) {
			dep.depName = dep.depName.replace(`${prefix}/`, "");
			if (specifyReplaceString) dep.autoReplaceStringTemplate = "{{packageName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}";
		}
	}
	if (dep.depName === "ubuntu" || dep.depName?.endsWith("/ubuntu")) dep.versioning = id$1;
	if ((dep.depName === "debian" || dep.depName?.endsWith("/debian")) && api.isVersion(dep.currentValue)) dep.versioning = id;
	if (dep.depName && quayRegex.test(dep.depName)) {
		const depName = dep.depName.replace(quayRegex, "quay.io");
		if (depName !== dep.depName) {
			dep.depName = depName;
			dep.autoReplaceStringTemplate = "{{packageName}}{{#if newValue}}:{{newValue}}{{/if}}{{#if newDigest}}@{{newDigest}}{{/if}}";
		}
	}
	return dep;
}
function extractPackageFile(content, _packageFile, config) {
	const sanitizedContent = content.replace(regEx(/^\uFEFF/), "");
	const deps = [];
	const stageNames = [];
	const args = {};
	const argsLines = {};
	let escapeChar = "\\\\";
	let lookForEscapeChar = true;
	let lookForSyntaxDirective = true;
	const lineFeed = sanitizedContent.includes("\r\n") ? "\r\n" : "\n";
	const lines = sanitizedContent.split(newlineRegex);
	for (let lineNumber = 0; lineNumber < lines.length;) {
		const lineNumberInstrStart = lineNumber;
		let instruction = lines[lineNumber];
		if (lookForEscapeChar) {
			const directivesMatch = regEx(/^[ \t]*#[ \t]*(?<directive>syntax|escape)[ \t]*=[ \t]*(?<escapeChar>\S)/i).exec(instruction);
			if (!directivesMatch) lookForEscapeChar = false;
			else if (directivesMatch.groups?.directive.toLowerCase() === "escape") {
				if (directivesMatch.groups?.escapeChar === "`") escapeChar = "`";
				lookForEscapeChar = false;
			}
		}
		if (lookForSyntaxDirective) {
			const syntaxRegex = regEx("^#[ \\t]*syntax[ \\t]*=[ \\t]*(?<image>\\S+)", "im");
			const syntaxMatch = instruction.match(syntaxRegex);
			if (syntaxMatch?.groups?.image) {
				const syntaxImage = syntaxMatch.groups.image;
				const lineNumberRanges = [[lineNumberInstrStart, lineNumber]];
				const dep = getDep(syntaxImage, true, config.registryAliases);
				dep.depType = "syntax";
				processDepForAutoReplace(dep, lineNumberRanges, lines, lineFeed);
				logger.trace({
					depName: dep.depName,
					currentValue: dep.currentValue,
					currentDigest: dep.currentDigest
				}, "Dockerfile # syntax");
				deps.push(dep);
			}
			lookForSyntaxDirective = false;
		}
		const lineContinuationRegex = regEx(escapeChar + "[ \\t]*$|^[ \\t]*#", "m");
		let lineLookahead = instruction;
		while (!lookForEscapeChar && !instruction.trimStart().startsWith("#") && lineContinuationRegex.test(lineLookahead)) {
			lineLookahead = lines[++lineNumber] || "";
			instruction += "\n" + lineLookahead;
		}
		const argMatch = regEx("^[ \\t]*ARG(?:" + escapeChar + "[ \\t]*\\r?\\n| |\\t|#.*?\\r?\\n)+(?<name>\\w+)[ =](?<value>\\S*)", "im").exec(instruction);
		if (argMatch?.groups?.name) {
			argsLines[argMatch.groups.name] = [lineNumberInstrStart, lineNumber];
			let argMatchValue = argMatch.groups?.value;
			if (argMatchValue.startsWith("\"") && argMatchValue.endsWith("\"")) argMatchValue = argMatchValue.slice(1, -1);
			args[argMatch.groups.name] = argMatchValue || "";
		}
		const fromRegex = new RegExp("^[ \\t]*FROM(?:" + escapeChar + "[ \\t]*\\r?\\n| |\\t|#.*?\\r?\\n|--platform=\\S+)+(?<image>\\S+)(?:(?:" + escapeChar + "[ \\t]*\\r?\\n| |\\t|#.*?\\r?\\n)+as[ \\t]+(?<name>\\S+))?", "im");
		const fromMatch = instruction.match(fromRegex);
		if (fromMatch?.groups?.image) {
			let fromImage = fromMatch.groups.image;
			const lineNumberRanges = [[lineNumberInstrStart, lineNumber]];
			if (fromImage.includes(variableMarker)) {
				const variables = extractVariables(fromImage);
				for (const [fullVariable, argName] of Object.entries(variables)) {
					const resolvedArgValue = args[argName];
					if (resolvedArgValue || resolvedArgValue === "") {
						fromImage = fromImage.replaceAll(fullVariable, resolvedArgValue);
						lineNumberRanges.push(argsLines[argName]);
					}
				}
			}
			if (fromMatch.groups?.name) {
				logger.debug(`Found a multistage build stage name: ${fromMatch.groups.name}`);
				stageNames.push(fromMatch.groups.name);
			}
			if (fromImage === "scratch") logger.debug("Skipping scratch");
			else if (fromImage && stageNames.includes(fromImage)) logger.debug(`Skipping alias FROM image:${fromImage}`);
			else {
				const dep = getDep(fromImage, true, config.registryAliases);
				processDepForAutoReplace(dep, lineNumberRanges, lines, lineFeed);
				logger.trace({
					depName: dep.depName,
					currentValue: dep.currentValue,
					currentDigest: dep.currentDigest
				}, "Dockerfile FROM");
				deps.push(dep);
			}
		}
		const copyFromRegex = new RegExp("^[ \\t]*COPY(?:" + escapeChar + "[ \\t]*\\r?\\n| |\\t|#.*?\\r?\\n|--[a-z]+(?:=[a-zA-Z0-9_.:-]+?)?)+--from=(?<image>\\S+)", "im");
		const copyFromMatch = instruction.match(copyFromRegex);
		if (copyFromMatch?.groups?.image) if (stageNames.includes(copyFromMatch.groups.image)) logger.debug({ image: copyFromMatch.groups.image }, "Skipping alias COPY --from");
		else if (Number.isNaN(Number(copyFromMatch.groups.image))) {
			const dep = getDep(copyFromMatch.groups.image, true, config.registryAliases);
			processDepForAutoReplace(dep, [[lineNumberInstrStart, lineNumber]], lines, lineFeed);
			logger.debug({
				depName: dep.depName,
				currentValue: dep.currentValue,
				currentDigest: dep.currentDigest
			}, "Dockerfile COPY --from");
			deps.push(dep);
		} else logger.debug({ image: copyFromMatch.groups.image }, "Skipping index reference COPY --from");
		const runMountFromRegex = regEx("^[ \\t]*RUN(?:" + escapeChar + "[ \\t]*\\r?\\n| |\\t|#.*?\\r?\\n|--[a-z]+(?:=[a-zA-Z0-9_.:-]+?)?)+--mount=(?:\\S*=\\S*,)*from=(?<image>[^, ]+)", "im");
		const runMountFromMatch = instruction.match(runMountFromRegex);
		if (runMountFromMatch?.groups?.image) if (stageNames.includes(runMountFromMatch.groups.image)) logger.debug({ image: runMountFromMatch.groups.image }, "Skipping alias RUN --mount=from");
		else {
			const dep = getDep(runMountFromMatch.groups.image, true, config.registryAliases);
			processDepForAutoReplace(dep, [[lineNumberInstrStart, lineNumber]], lines, lineFeed);
			logger.debug({
				depName: dep.depName,
				currentValue: dep.currentValue,
				currentDigest: dep.currentDigest
			}, "Dockerfile RUN --mount=from");
			deps.push(dep);
		}
		lineNumber += 1;
	}
	if (!deps.length) return null;
	for (const d of deps) d.depType ??= "stage";
	deps[deps.length - 1].depType = "final";
	return { deps };
}
//#endregion
export { extractPackageFile, getDep };

//# sourceMappingURL=extract.js.map