import { get, set } from "../../../../../util/cache/memory/index.js";
import { newlineRegex, regEx } from "../../../../../util/regex.js";
import { coerceString } from "../../../../../util/string.js";
import { logger } from "../../../../../logger/index.js";
import { isHttpUrl, joinUrlParts } from "../../../../../util/url.js";
import { detectPlatform } from "../../../../../util/common.js";
import { get as get$1, set as set$1 } from "../../../../../util/cache/package/index.js";
import { linkify } from "../../../../../util/markdown.js";
import { getReleaseList as getReleaseList$1, getReleaseNotesMd as getReleaseNotesMd$1 } from "./bitbucket/index.js";
import { getReleaseNotesMd as getReleaseNotesMd$2 } from "./bitbucket-server/index.js";
import { getReleaseList as getReleaseList$2, getReleaseNotesMd as getReleaseNotesMd$3 } from "./forgejo/index.js";
import { getReleaseList as getReleaseList$3, getReleaseNotesMd as getReleaseNotesMd$4 } from "./gitea/index.js";
import { getReleaseList as getReleaseList$4, getReleaseNotesMd as getReleaseNotesMd$5 } from "./github/index.js";
import { getReleaseList as getReleaseList$5, getReleaseNotesMd as getReleaseNotesMd$6 } from "./gitlab/index.js";
import { isDate, isUndefined } from "@sindresorhus/is";
import { DateTime } from "luxon";
import MarkdownIt from "markdown-it";
//#region lib/workers/repository/update/pr/changelog/release-notes.ts
const markdown = new MarkdownIt("zero");
markdown.enable([
	"heading",
	"lheading",
	"fence"
]);
const repositoriesToSkipMdFetching = ["facebook/react-native"];
async function getReleaseList(project, release) {
	logger.trace("getReleaseList()");
	const { apiBaseUrl, repository, type } = project;
	try {
		switch (type) {
			case "bitbucket": return getReleaseList$1(project, release);
			case "bitbucket-server":
				logger.trace("Unsupported Bitbucket Server feature. Skipping release fetching.");
				return [];
			case "forgejo": return await getReleaseList$2(project, release);
			case "gitea": return await getReleaseList$3(project, release);
			case "github": return await getReleaseList$4(project, release);
			case "gitlab": return await getReleaseList$5(project, release);
			default:
				logger.warn({
					apiBaseUrl,
					repository,
					type
				}, "Invalid project type");
				return [];
		}
	} catch (err) 	/* istanbul ignore next */ {
		if (err.statusCode === 404) logger.debug({
			repository,
			type,
			apiBaseUrl
		}, "getReleaseList 404");
		else logger.debug({
			repository,
			type,
			apiBaseUrl,
			err
		}, "getReleaseList error");
	}
	return [];
}
function getCachedReleaseList(project, release) {
	const { repository, apiBaseUrl } = project;
	const cacheKey = `getReleaseList-${apiBaseUrl}-${repository}`;
	const cachedResult = get(cacheKey);
	// istanbul ignore if
	if (cachedResult !== void 0) return cachedResult;
	const promisedRes = getReleaseList(project, release);
	set(cacheKey, promisedRes);
	return promisedRes;
}
function massageBody(input, baseUrl) {
	let body = coerceString(input);
	body = body.replace(regEx(/\r\n/g), "\n");
	body = body.replace(regEx(/^<a name="[^"]*"><\/a>\n/), "");
	body = body.replace(regEx(`^##? \\[[^\\]]*\\]\\(${baseUrl}[^/]*/[^/]*/compare/.*?\\n`, void 0, false), "");
	body = `\n${body}\n`.replace(regEx(`\\n${baseUrl}[^/]+/[^/]+/compare/[^\\n]+(\\n|$)`), "\n");
	body = body.split(regEx(/(```[\s\S]*?```)/g)).map((part) => part.startsWith("```") ? part : part.replace(regEx(/\n\s*####? /g), "\n##### ").replace(regEx(/\n\s*## /g), "\n#### ").replace(regEx(/\n\s*# /g), "\n### ")).join("");
	return body.trim();
}
function massageName(input, version) {
	let name = input ?? "";
	if (version) name = name.replace(RegExp(`^(Release )?v?${version}`, "i"), "").trim();
	name = name.trim();
	if (!name.length) return;
	return name;
}
async function getReleaseNotes(project, release, config) {
	const { packageName, depName, repository } = project;
	const { version, gitRef } = release;
	logger.trace(`getReleaseNotes(${repository}, ${version}, ${packageName}, ${depName})`);
	const releases = await getCachedReleaseList(project, release);
	logger.trace({ releases }, "Release list from getReleaseList");
	let releaseNotes = null;
	let matchedRelease = getExactReleaseMatch(packageName, depName, version, releases);
	if (isUndefined(matchedRelease)) matchedRelease = releases.find((r) => r.tag === version || r.tag === `v${version}` || r.tag === gitRef || r.tag === `v${gitRef}`);
	if (isUndefined(matchedRelease) && config.extractVersion) {
		const extractVersionRegEx = regEx(config.extractVersion);
		matchedRelease = releases.find((r) => {
			return version === extractVersionRegEx.exec(r.tag)?.groups?.version;
		});
	}
	releaseNotes = await releaseNotesResult(matchedRelease, project);
	logger.trace({ releaseNotes });
	return releaseNotes;
}
function getExactReleaseMatch(packageName, depName, version, releases) {
	const exactReleaseReg = regEx(`(?:^|/)(?:${packageName}|${depName})[@_-]v?${version}`);
	return releases.filter((r) => r.tag?.endsWith(version)).find((r) => exactReleaseReg.test(r.tag));
}
async function releaseNotesResult(releaseMatch, project) {
	if (!releaseMatch) return null;
	const { baseUrl, repository } = project;
	const releaseNotes = releaseMatch;
	if (detectPlatform(baseUrl) === "gitlab") releaseNotes.url = `${baseUrl}${repository}/tags/${releaseMatch.tag}`;
	else releaseNotes.url = releaseMatch.url ? releaseMatch.url : `${baseUrl}${repository}/releases/${releaseMatch.tag}`;
	releaseNotes.body = massageBody(releaseNotes.body, baseUrl);
	releaseNotes.name = massageName(releaseNotes.name, releaseNotes.tag);
	if (releaseNotes.body.length || releaseNotes.name?.length) try {
		if (baseUrl !== "https://gitlab.com/") releaseNotes.body = await linkify(releaseNotes.body, { repository: `${baseUrl}${repository}` });
	} catch (err) 	/* istanbul ignore next */ {
		logger.warn({
			err,
			baseUrl,
			repository
		}, "Error linkifying");
	}
	else return null;
	return releaseNotes;
}
function sectionize(text, level) {
	const sections = [];
	const lines = text.split(newlineRegex);
	markdown.parse(text, void 0).forEach((token) => {
		if (token.type === "heading_open") {
			const lev = +token.tag.substring(1);
			if (lev <= level) sections.push([lev, token.map[0]]);
		}
	});
	sections.push([-1, lines.length]);
	const result = [];
	for (let i = 1; i < sections.length; i += 1) {
		const [lev, start] = sections[i - 1];
		const [, end] = sections[i];
		if (lev === level) result.push(lines.slice(start, end).join("\n"));
	}
	return result;
}
async function getReleaseNotesMdFileInner(project) {
	const { repository, type } = project;
	const apiBaseUrl = project.apiBaseUrl;
	const sourceDirectory = project.sourceDirectory;
	try {
		switch (type) {
			case "bitbucket": return await getReleaseNotesMd$1(repository, apiBaseUrl, sourceDirectory);
			case "bitbucket-server": return await getReleaseNotesMd$2(repository, apiBaseUrl, sourceDirectory);
			case "forgejo": return await getReleaseNotesMd$3(repository, apiBaseUrl, sourceDirectory);
			case "gitea": return await getReleaseNotesMd$4(repository, apiBaseUrl, sourceDirectory);
			case "github": return await getReleaseNotesMd$5(repository, apiBaseUrl, sourceDirectory);
			case "gitlab": return await getReleaseNotesMd$6(repository, apiBaseUrl, sourceDirectory);
			default:
				logger.warn({
					apiBaseUrl,
					repository,
					type
				}, "Invalid project type");
				return null;
		}
	} catch (err) 	/* istanbul ignore next */ {
		if (err.statusCode === 404) logger.debug({
			repository,
			type,
			apiBaseUrl
		}, "Error 404 getting changelog md");
		else logger.debug({
			err,
			repository,
			type,
			apiBaseUrl
		}, "Error getting changelog md");
	}
	return null;
}
function getReleaseNotesMdFile(project) {
	const { sourceDirectory, repository, apiBaseUrl } = project;
	const cacheKey = sourceDirectory ? `getReleaseNotesMdFile@v2-${repository}-${sourceDirectory}-${apiBaseUrl}` : `getReleaseNotesMdFile@v2-${repository}-${apiBaseUrl}`;
	const cachedResult = get(cacheKey);
	// istanbul ignore if
	if (cachedResult !== void 0) return cachedResult;
	const promisedRes = getReleaseNotesMdFileInner(project);
	set(cacheKey, promisedRes);
	return promisedRes;
}
async function getReleaseNotesMd(project, release) {
	const { baseUrl, repository, packageName } = project;
	const version = release.version;
	logger.trace(`getReleaseNotesMd(${repository}, ${version})`);
	if (shouldSkipChangelogMd(repository)) return null;
	const changelog = await getReleaseNotesMdFile(project);
	if (!changelog) return null;
	const { changelogFile } = changelog;
	const changelogMd = changelog.changelogMd.replace(regEx(/\n\s*<a name="[^"]*">.*?<\/a>\n/g), "\n");
	for (const level of [
		1,
		2,
		3,
		4,
		5,
		6,
		7
	]) {
		const changelogParsed = sectionize(changelogMd, level);
		if (changelogParsed.length >= 2) for (const section of changelogParsed) try {
			const [heading] = section.replace(regEx(/[[\]()]/g), " ").split(newlineRegex);
			const title = heading.replace(regEx(/^\s*#*\s*/), "").split(" ").filter(Boolean);
			const body = section.replace(regEx(/.*?\n(-{3,}\n)?/), "").trim();
			const notesSourceUrl = getNotesSourceUrl(baseUrl, repository, project, changelogFile);
			const url = `${notesSourceUrl}#${title.filter((word) => !isHttpUrl(word)).join("-").replace(regEx(/[^A-Za-z0-9-]/g), "")}`;
			for (const word of title) if (word.includes(version) && !isHttpUrl(word)) {
				logger.trace({ body }, "Found release notes for v" + version);
				return {
					body: await linkifyBody(project, body),
					url,
					notesSourceUrl
				};
			}
			const releasesRegex = regEx(/([0-9]{4}-[0-9]{2}-[0-9]{2})/);
			if (packageName && heading.search(releasesRegex) !== -1) {
				const linkRefDefRegex = regEx(/^\s*\[[^\]]+\]:\s*\S+/);
				if (body.split("\n").some((line) => line.includes(packageName) && line.includes(version) && !isHttpUrl(line) && !linkRefDefRegex.test(line))) {
					logger.trace({ body }, "Found release notes for v" + version);
					return {
						body: await linkifyBody(project, body),
						url,
						notesSourceUrl
					};
				}
			}
		} catch (err) 		/* istanbul ignore next */ {
			logger.warn({
				file: changelogFile,
				err
			}, `Error parsing changelog file`);
		}
		logger.trace({ repository }, `No level ${level} changelogs headings found`);
	}
	logger.trace({
		repository,
		version
	}, `No entry found in ${changelogFile}`);
	return null;
}
/**
* Determine how long to cache release notes based on when the version was released.
*
* It's not uncommon for release notes to be updated shortly after the release itself,
* so only cache for about an hour when the release is less than a week old. Otherwise,
* cache for days.
*/
function releaseNotesCacheMinutes(releaseDate) {
	const dt = isDate(releaseDate) ? DateTime.fromJSDate(releaseDate) : DateTime.fromISO(releaseDate);
	const now = DateTime.local();
	if (!dt.isValid || now.diff(dt, "days").days < 7) return 55;
	if (now.diff(dt, "months").months < 6) return 1435;
	return 14495;
}
async function addReleaseNotes(input, config) {
	if (!input?.versions || !input.project?.type) {
		logger.debug("Missing project or versions");
		return input ?? null;
	}
	const output = {
		...input,
		versions: [],
		hasReleaseNotes: false
	};
	const { repository, sourceDirectory, type: projectType } = input.project;
	const cacheNamespace = `changelog-${projectType}-notes@v2`;
	const cacheKeyPrefix = sourceDirectory ? `${repository}:${sourceDirectory}` : `${repository}`;
	for (const v of input.versions) {
		let releaseNotes;
		const cacheKey = `${cacheKeyPrefix}:${v.version}`;
		releaseNotes = await get$1(cacheNamespace, cacheKey);
		releaseNotes ??= await getReleaseNotesMd(input.project, v);
		releaseNotes ??= await getReleaseNotes(input.project, v, config);
		if (!releaseNotes && v.compare.url) releaseNotes = {
			url: v.compare.url,
			notesSourceUrl: ""
		};
		const cacheMinutes = releaseNotesCacheMinutes(v.date);
		await set$1(cacheNamespace, cacheKey, releaseNotes, cacheMinutes);
		output.versions.push({
			...v,
			releaseNotes
		});
		if (releaseNotes) output.hasReleaseNotes = true;
	}
	return output;
}
/**
* Skip fetching changelog/release-notes markdown files.
* Will force a fallback to using GitHub release notes
*/
function shouldSkipChangelogMd(repository) {
	return repositoriesToSkipMdFetching.includes(repository);
}
function getNotesSourceUrl(baseUrl, repository, project, changelogFile) {
	if (project.type === "bitbucket-server") {
		const [projectKey, repositorySlug] = repository.split("/");
		return joinUrlParts(baseUrl, "projects", projectKey, "repos", repositorySlug, "browse", changelogFile, "?at=HEAD");
	}
	return joinUrlParts(baseUrl, repository, project.type === "bitbucket" ? "src" : "blob", "HEAD", changelogFile);
}
async function linkifyBody({ baseUrl, repository }, bodyStr) {
	const body = massageBody(bodyStr, baseUrl);
	if (body?.length) try {
		return await linkify(body, { repository: `${baseUrl}${repository}` });
	} catch (err) 	/* istanbul ignore next */ {
		logger.warn({
			body,
			err
		}, "linkify error");
	}
	return body;
}
//#endregion
export { addReleaseNotes };

//# sourceMappingURL=release-notes.js.map