import { sanitize } from "../../../util/sanitize.js";
import { logger } from "../../../logger/index.js";
import { Lazy } from "../../../util/lazy.js";
import { workItemTrackingApi } from "./azure-got-wrapper.js";
import { getWorkItemTitle } from "./util.js";
//#region lib/modules/platform/azure/issue.ts
const workItemType = "Issue";
const defaultOpenState = "New";
const defaultClosedState = "Closed";
const openCategories = ["Proposed", "InProgress"];
const closedCategories = [
	"Completed",
	"Resolved",
	"Removed"
];
/**
* Return the names of the states whose category is one of `categories`,
* preserving the order Azure DevOps returns them in (workflow order).
*/
function namesByCategory(stateColors, categories) {
	return stateColors.filter((s) => s.category && categories.includes(s.category) && s.name).map((s) => s.name);
}
var IssueService = class {
	config;
	workItemStates;
	constructor(config) {
		this.config = config;
		this.workItemStates = new Lazy(() => this.resolveWorkItemStates());
	}
	/**
	* Resolve the concrete open/closed state names for the `Issue` work item type
	* from its process, so Renovate does not depend on hardcoded state names that
	* only exist in some Azure DevOps processes. Falls back to the historical
	* `New`/`Closed` values if the states cannot be fetched.
	*/
	async resolveWorkItemStates() {
		const states = {
			open: defaultOpenState,
			closed: defaultClosedState,
			closedNames: /* @__PURE__ */ new Set([defaultClosedState])
		};
		try {
			const stateColors = await (await workItemTrackingApi()).getWorkItemTypeStates(this.config.project, workItemType);
			if (stateColors?.length) {
				const openNames = namesByCategory(stateColors, openCategories);
				const completed = stateColors.find((s) => s.category === "Completed" && s.name);
				const closedNames = namesByCategory(stateColors, closedCategories);
				states.open = openNames[0] ?? stateColors[0].name ?? states.open;
				if (closedNames.length) {
					states.closed = completed?.name ?? closedNames[0];
					states.closedNames = new Set(closedNames);
				}
			}
		} catch (err) {
			logger.debug({ err }, "Azure: could not resolve work item states, using default state names");
		}
		return states;
	}
	async findIssue(title) {
		logger.debug(`findIssue(${title})`);
		try {
			const finalTitle = getWorkItemTitle(title, this.config.repository);
			return (await this.getIssueList(finalTitle))[0] ?? null;
		} catch (err) {
			logger.error({ err }, "Error finding issue");
			return null;
		}
	}
	/**
	* Whether the project's process defines the `Issue` work item type. Some
	* processes (e.g. Scrum) do not, in which case creating one fails; checking
	* up front lets us log an actionable message instead of a cryptic error.
	*/
	async hasWorkItemType(azureApiWit) {
		return (await azureApiWit.getWorkItemTypes(this.config.project)).some((t) => t.name === workItemType);
	}
	async getIssueList(titleFilter) {
		logger.debug("getIssueList()");
		try {
			const azureApiWit = await workItemTrackingApi();
			let wiql = `
        SELECT [System.Id]
        FROM WorkItems
        WHERE [System.WorkItemType] = '${workItemType}'
          AND [System.TeamProject] = '${this.config.project}'
      `;
			if (titleFilter) {
				const escapedTitle = titleFilter.replace(/'/g, "''");
				wiql += ` AND [System.Title] = '${escapedTitle}'`;
			}
			const result = await azureApiWit.queryByWiql({ query: wiql });
			if (!result.workItems?.length) {
				logger.debug("getIssueList() no work items found");
				return [];
			}
			const workItemIds = result.workItems.map((wi) => wi.id);
			const workItems = await azureApiWit.getWorkItems(workItemIds, [
				"System.Id",
				"System.Title",
				"System.State",
				"System.Description",
				"System.CreatedDate",
				"System.ChangedDate"
			]);
			const { closedNames } = await this.workItemStates.getValue();
			return workItems.map((wi) => ({
				number: wi.id,
				title: wi.fields["System.Title"],
				state: closedNames.has(wi.fields["System.State"]) ? "closed" : "open",
				body: wi.fields["System.Description"],
				createdAt: wi.fields["System.CreatedDate"],
				lastModified: wi.fields["System.ChangedDate"]
			}));
		} catch (err) {
			logger.warn({ err }, "Error fetching issue list");
			return [];
		}
	}
	async ensureIssueClosing(title) {
		logger.debug(`ensureIssueClosing(${title})`);
		try {
			const issue = await this.findIssue(title);
			if (issue?.state === "open" && issue.number) {
				const azureApiWit = await workItemTrackingApi();
				const { closed } = await this.workItemStates.getValue();
				await azureApiWit.updateWorkItem(void 0, [{
					op: "replace",
					path: "/fields/System.State",
					value: closed
				}], issue.number, this.config.project);
				logger.debug(`Closed issue #${issue.number}: ${title}`);
			}
		} catch (err) {
			logger.error({ err }, "Error closing issue");
		}
	}
	async ensureIssue({ title, body, once = false, shouldReOpen = true }) {
		logger.debug(`ensureIssue()`);
		try {
			const azureApiWit = await workItemTrackingApi();
			const finalTitle = getWorkItemTitle(title, this.config.repository);
			const issues = await this.getIssueList(finalTitle);
			const { open, closed } = await this.workItemStates.getValue();
			const openIssues = issues.filter((issue) => issue.state === "open");
			if (openIssues.length > 1) for (let i = 1; i < openIssues.length; i++) {
				const issueNumber = openIssues[i].number;
				if (issueNumber) {
					await azureApiWit.updateWorkItem(void 0, [{
						op: "replace",
						path: "/fields/System.State",
						value: closed
					}], issueNumber, this.config.project);
					logger.info(`Closed duplicate issue #${issueNumber}`);
				}
			}
			const existingIssue = openIssues[0] ?? issues.find((issue) => issue.state === "closed");
			if (existingIssue) {
				if (existingIssue.state === "closed" && once) {
					logger.debug("Issue already closed - skipping update");
					return null;
				}
				if (existingIssue.state === "closed" && shouldReOpen) {
					if (!existingIssue.number) {
						logger.warn("Cannot reopen issue without number");
						return null;
					}
					await azureApiWit.updateWorkItem(void 0, [
						{
							op: "replace",
							path: "/fields/System.State",
							value: open
						},
						{
							op: "replace",
							path: "/fields/System.Title",
							value: finalTitle
						},
						{
							op: "replace",
							path: "/fields/System.Description",
							value: sanitize(body)
						},
						{
							op: "replace",
							path: "/multilineFieldsFormat/System.Description",
							value: "Markdown"
						}
					], existingIssue.number, this.config.project);
					logger.debug(`Reopened issue #${existingIssue.number}`);
					return "updated";
				}
				if (existingIssue.state === "open") {
					if (existingIssue.title !== finalTitle || existingIssue.body !== body) {
						if (!existingIssue.number) {
							logger.warn("Cannot update issue without number");
							return null;
						}
						await azureApiWit.updateWorkItem(void 0, [
							{
								op: "replace",
								path: "/fields/System.Title",
								value: finalTitle
							},
							{
								op: "replace",
								path: "/fields/System.Description",
								value: sanitize(body)
							},
							{
								op: "replace",
								path: "/multilineFieldsFormat/System.Description",
								value: "Markdown"
							}
						], existingIssue.number, this.config.project);
						logger.debug(`Updated issue #${existingIssue.number}`);
						return "updated";
					}
					logger.debug(`Issue #${existingIssue.number} is already up-to-date`);
					return "updated";
				}
			}
			if (!await this.hasWorkItemType(azureApiWit)) {
				logger.warn({
					workItemType,
					project: this.config.project
				}, "Azure: work item type does not exist in project (or the token lacks permission to it); skipping issue. The Dependency Dashboard needs a process that defines this work item type.");
				return null;
			}
			const newWorkItem = await azureApiWit.createWorkItem(void 0, [
				{
					op: "add",
					path: "/fields/System.WorkItemType",
					value: workItemType
				},
				{
					op: "add",
					path: "/fields/System.Title",
					value: finalTitle
				},
				{
					op: "add",
					path: "/fields/System.Description",
					value: sanitize(body)
				},
				{
					op: "add",
					path: "/multilineFieldsFormat/System.Description",
					value: "Markdown"
				}
			], this.config.project, workItemType);
			if (!newWorkItem?.id) {
				logger.warn("Azure: work item creation returned no result; skipping issue");
				return null;
			}
			logger.debug(`Created new issue #${newWorkItem.id}`);
			return "created";
		} catch (err) {
			logger.warn({ err }, "Error ensuring issue");
			return null;
		}
	}
};
//#endregion
export { IssueService };

//# sourceMappingURL=issue.js.map