"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.GitLabTicketTrackerClient = void 0;
// biome-ignore-all lint/style/useNamingConvention: GitLab REST payloads mirror upstream field names.
// biome-ignore-all lint/nursery/noExcessiveLinesPerFile: GitLab tracker behavior stays together to mirror existing provider clients.
const catalog_1 = require("../../i18n/catalog");
const constants_1 = require("../constants");
const api_support_1 = require("../gitlab/api-support");
const errors_1 = require("./errors");
const label_workflow_state_store_1 = require("./label-workflow-state-store");
const comment_reactions_1 = require("./comment-reactions");
const comment_support_1 = require("./comment-support");
const project_ref_1 = require("../gitlab/project-ref");
const issue_normalization_1 = require("./issue-normalization");
const issue_search_1 = require("./issue-search");
const types_1 = require("./types");
const workflow_labels_1 = require("./workflow-labels");
const GITLAB_TICKET_TRACKER_ERROR_NAME = "GitLabTicketTrackerError";
class GitLabTicketTrackerClient {
    config;
    token;
    locale;
    projectApiBaseUrl;
    workflowStateStore;
    resolvedWorkflowLabels;
    requiredWorkflowLabels;
    commentReactions;
    constructor(config, token, locale = catalog_1.DEFAULT_APP_LOCALE) {
        this.config = config;
        this.token = token;
        this.locale = locale;
        this.projectApiBaseUrl = (0, project_ref_1.buildGitLabProjectApiBaseUrl)(config.projectRef);
        this.commentReactions = [
            ...(config.commentReactions ?? comment_reactions_1.DEFAULT_TRACKER_COMMENT_REACTIONS),
        ];
        this.workflowStateStore = new label_workflow_state_store_1.LabelWorkflowStateStore(config.workflowLabels, locale);
        this.resolvedWorkflowLabels = (0, workflow_labels_1.resolveWorkflowLabels)(config.workflowLabels);
        this.requiredWorkflowLabels = (0, workflow_labels_1.buildManagedLabelDefinitions)({
            workflowLabels: this.config.workflowLabels,
            locale: this.locale,
        });
    }
    async listIssues(query) {
        const normalizedQuery = (0, issue_normalization_1.normalizeTrackerIssueListQuery)(query);
        return (0, issue_normalization_1.collectMappedTrackerIssues)((await this.listIssueRecords(normalizedQuery)).map((issue) => (0, issue_normalization_1.normalizeMappedTrackerIssueRecord)({
            issueRecord: issue,
            requireWorkflowState: true,
            parseWorkflowState: (labelNames) => this.workflowStateStore.parse(labelNames),
        })), normalizedQuery.workflowStatus);
    }
    async listIssueRecords(query) {
        const normalizedQuery = (0, issue_normalization_1.normalizeTrackerIssueListQuery)(query);
        const [issues, mergeRequests] = await Promise.all([
            this.listIssueRecordsOnly(normalizedQuery),
            this.listMergeRequestRecords(normalizedQuery),
        ]);
        return [...issues, ...mergeRequests];
    }
    async searchIssueRecords(query) {
        const search = (0, issue_search_1.normalizeIssueSearchQuery)(query);
        if (search === null) {
            return [];
        }
        const [issues, mergeRequests] = await Promise.all([
            this.searchIssueRecordsOnly(query, search),
            query.includePullRequests === true
                ? this.searchMergeRequestRecords(query, search)
                : Promise.resolve([]),
        ]);
        const matches = (0, issue_search_1.sortIssueSearchResultsByUpdatedAtDesc)([
            ...issues,
            ...mergeRequests,
        ]);
        return (0, issue_search_1.limitIssueSearchResults)(matches, search);
    }
    async listServiceManagedDeltaIssues(query) {
        const normalizedQuery = (0, issue_normalization_1.normalizeServiceManagedDeltaIssueListQuery)(query);
        const issuesById = new Map();
        for (const labelName of (0, workflow_labels_1.getNonFollowupManagedLabelNames)(this.resolvedWorkflowLabels)) {
            const issues = await this.listIssues({
                state: normalizedQuery.state ?? constants_1.PLATFORM_ISSUE_LIST_STATES.all,
                since: normalizedQuery.since,
                labelNames: [labelName],
                includeSourceBranchName: normalizedQuery.includeSourceBranchName,
            });
            for (const issue of issues) {
                issuesById.set(issue.id, issue);
            }
        }
        return [...issuesById.values()];
    }
    async listServiceConfigScanActivity(input) {
        const previousCursor = input.cursor?.kind === "gitlab_project_events" ? input.cursor : null;
        const previousEventId = previousCursor?.lastSeenEventId ?? null;
        const candidateIssueIds = new Set();
        let latestSeenEventId = previousEventId;
        let reachedCursor = false;
        let nextUrl = buildGitLabProjectEventsUrl(this.projectApiBaseUrl);
        while (nextUrl !== null && !reachedCursor) {
            const response = await this.request("GET", nextUrl);
            const events = (await response.json());
            if (latestSeenEventId === previousEventId && events[0]) {
                latestSeenEventId = String(events[0].id);
            }
            if (input.initializeOnly === true) {
                break;
            }
            for (const event of events) {
                const eventId = String(event.id);
                if (previousEventId !== null &&
                    compareGitLabEventIds(eventId, previousEventId) <= 0) {
                    reachedCursor = true;
                    break;
                }
                const issueId = resolveGitLabConfigScanActivityIssueId(event);
                if (issueId !== null) {
                    candidateIssueIds.add(issueId);
                }
            }
            const nextPage = response.headers.get("x-next-page");
            if (!nextPage || reachedCursor) {
                nextUrl = null;
            }
            else {
                const currentUrl = new URL(nextUrl);
                currentUrl.searchParams.set("page", nextPage);
                nextUrl = currentUrl.toString();
            }
        }
        return {
            candidateIssueIds: [...candidateIssueIds],
            nextCursor: {
                kind: "gitlab_project_events",
                lastSeenEventId: latestSeenEventId,
            },
            completed: true,
        };
    }
    async getIssue(issueId) {
        const issueRecord = await this.getIssueRecord(issueId);
        const issue = (0, issue_normalization_1.normalizeMappedTrackerIssueRecord)({
            issueRecord,
            requireWorkflowState: true,
            parseWorkflowState: (labelNames) => this.workflowStateStore.parse(labelNames),
        });
        if (!issue) {
            throw (0, errors_1.createTrackerMissingWorkflowLabelSetError)(this.locale, issueId);
        }
        return issue;
    }
    async getIssueRecord(issueId) {
        if (isGitLabMergeRequestTrackerId(issueId)) {
            return this.getMergeRequestRecord(parseGitLabMergeRequestTrackerId(issueId));
        }
        return this.getIssueRecordByIid(parseGitLabIssueTrackerId(issueId));
    }
    async getPullRequestThreadRecord(input) {
        try {
            return await this.getMergeRequestRecord(input.pullRequestNumber);
        }
        catch (error) {
            if ((0, api_support_1.isGitLabNotFoundError)(error)) {
                return null;
            }
            throw error;
        }
    }
    async getIssueReference(issueId) {
        const issue = await this.getIssueRecord(issueId);
        return {
            id: issue.id,
            displayId: issue.displayId,
            title: issue.title,
            baseTitle: issue.baseTitle,
            isOpen: issue.isOpen,
        };
    }
    async getIssueCleanupState(issueId) {
        return { isOpen: (await this.getIssueRecord(issueId)).isOpen };
    }
    async createIssue(input) {
        assertGitLabProjectBindingSupported(input.projectRef);
        const milestoneId = await this.resolveMilestoneId(input.milestoneRef ?? null);
        const issue = await this.requestJson("POST", `${this.projectApiBaseUrl}/issues`, {
            title: input.title,
            description: input.body,
            labels: input.labelNames.join(","),
            assignee_ids: await (0, api_support_1.resolveGitLabUserIds)({
                projectRef: this.config.projectRef,
                token: this.token,
                errorName: GITLAB_TICKET_TRACKER_ERROR_NAME,
                logins: input.assignees,
            }),
            ...(milestoneId !== null ? { milestone_id: milestoneId } : {}),
        });
        return (0, issue_normalization_1.mapMutatedTrackerIssueOrThrow)({
            issue,
            issueId: String(issue.iid),
            mapIssue: async (mappedIssue) => this.mapIssue(mappedIssue),
            createMissingWorkflowLabelSetError: (mappedIssueId) => (0, errors_1.createTrackerMissingWorkflowLabelSetError)(this.locale, mappedIssueId),
        });
    }
    async patchIssueRecord(issueId, input) {
        return this.patchIssueLike(issueId, input);
    }
    async patchIssue(issueId, input) {
        const issueRecord = await this.patchIssueLike(issueId, input);
        const issue = (0, issue_normalization_1.normalizeMappedTrackerIssueRecord)({
            issueRecord,
            requireWorkflowState: true,
            parseWorkflowState: (labelNames) => this.workflowStateStore.parse(labelNames),
        });
        if (!issue) {
            throw (0, errors_1.createTrackerMissingWorkflowLabelSetError)(this.locale, issueId);
        }
        return issue;
    }
    async closeIssue(issueId) {
        if (isGitLabMergeRequestTrackerId(issueId)) {
            await this.requestJson("PUT", `${this.projectApiBaseUrl}/merge_requests/${parseGitLabMergeRequestTrackerId(issueId)}`, { state_event: "close" });
            return;
        }
        await this.requestJson("PUT", `${this.projectApiBaseUrl}/issues/${parseGitLabIssueTrackerId(issueId)}`, { state_event: "close" });
    }
    async listBlockingDependencies(issueId) {
        if (isGitLabMergeRequestTrackerId(issueId)) {
            return [];
        }
        const issueIid = parseGitLabIssueTrackerId(issueId);
        const links = await this.requestJson("GET", `${this.projectApiBaseUrl}/issues/${issueIid}/links`);
        return links.flatMap((link) => {
            if (link.link_type !== "is_blocked_by") {
                return [];
            }
            const issue = resolveLinkedIssue(link);
            return issue ? [mapIssueDependency(issue)] : [];
        });
    }
    async addBlockingDependency(issueId, blockedByIssueId) {
        const projectId = await this.getProjectId();
        await this.requestJson("POST", `${this.projectApiBaseUrl}/issues/${parseGitLabIssueTrackerId(issueId)}/links`, {
            target_project_id: projectId,
            target_issue_iid: parseGitLabIssueTrackerId(blockedByIssueId),
            link_type: "is_blocked_by",
        });
    }
    async removeBlockingDependency(issueId, blockedByIssueId) {
        const issueIid = parseGitLabIssueTrackerId(issueId);
        const blockedByIid = parseGitLabIssueTrackerId(blockedByIssueId);
        const links = await this.requestJson("GET", `${this.projectApiBaseUrl}/issues/${issueIid}/links`);
        const link = links.find((entry) => {
            if (entry.link_type !== "is_blocked_by") {
                return false;
            }
            const linkedIssue = resolveLinkedIssue(entry);
            return linkedIssue?.iid === blockedByIid;
        });
        if (link?.issue_link_id === undefined || link.issue_link_id === null) {
            return;
        }
        await this.requestJson("DELETE", `${this.projectApiBaseUrl}/issues/${issueIid}/links/${link.issue_link_id}`);
    }
    async getAuthenticatedUserLogin() {
        const user = await this.requestJson("GET", `${(0, project_ref_1.parseGitLabProjectRef)(this.config.projectRef).origin}/api/v4/user`);
        return user.username ?? "";
    }
    async getRepositoryOwnerKind() {
        return "organization";
    }
    async getOrganizationMembership(_login) {
        return "unknown";
    }
    async getProjectMembership(_login) {
        return "unknown";
    }
    async listComments(issueId, options) {
        const target = resolveGitLabCommentTarget(issueId, await this.getIssueRecord(issueId));
        const timeline = options?.timeline ?? "full";
        if (target.kind === "mr" && timeline !== "issue-only") {
            return this.listMergeRequestDiscussionComments(target);
        }
        const notes = await this.requestJson("GET", `${this.projectApiBaseUrl}/${target.apiSegment}/${target.iid}/notes`);
        return notes
            .filter((note) => note.system !== true)
            .map((note) => mapNote(note, target))
            .sort(comment_support_1.compareTrackerComments);
    }
    async acknowledgeComment(comment) {
        if (comment.kind !== types_1.TRACKER_ACKNOWLEDGEABLE_COMMENT_KINDS.issueComment &&
            comment.kind !==
                types_1.TRACKER_ACKNOWLEDGEABLE_COMMENT_KINDS.pullRequestReviewComment) {
            return;
        }
        const reaction = (0, comment_reactions_1.selectCommentReaction)(comment.id, this.commentReactions);
        if (reaction === null) {
            return;
        }
        const target = parseGitLabCommentId(comment.id);
        const url = new URL(`${this.projectApiBaseUrl}/${target.apiSegment}/${target.iid}/notes/${target.noteId}/award_emoji`);
        url.searchParams.set("name", reaction);
        await this.requestJson("POST", url.toString());
    }
    async createComment(issueId, body) {
        const target = resolveGitLabCommentTarget(issueId, await this.getIssueRecord(issueId));
        const note = await this.requestJson("POST", `${this.projectApiBaseUrl}/${target.apiSegment}/${target.iid}/notes`, { body });
        return mapNote(note, target);
    }
    async updateComment(commentId, body) {
        const target = await this.resolveCommentTargetFromParsedId(parseGitLabCommentId(commentId));
        const note = await this.requestJson("PUT", `${this.projectApiBaseUrl}/${target.apiSegment}/${target.iid}/notes/${target.noteId}`, { body });
        return mapNote(note, target);
    }
    async replaceAssignees(issueId, assignees) {
        await this.patchIssueLike(issueId, { assignees });
    }
    async updateIssueTitle(issueId, title) {
        await this.patchIssueLike(issueId, { title });
    }
    resetVolatileIssueReads(_issueId) { }
    async syncWorkflowState(input) {
        const currentLabelNames = input.currentLabelNames ??
            (await this.getIssueRecord(input.issueId)).labelNames ??
            [];
        const syncInput = {
            workflowLabels: this.config.workflowLabels,
            currentLabelNames,
            next: input.next,
        };
        if (input.labelProjection !== undefined) {
            syncInput.labelProjection = input.labelProjection;
        }
        if (input.auxiliaryManagedLabels !== undefined) {
            syncInput.auxiliaryManagedLabels = input.auxiliaryManagedLabels;
        }
        const nextLabels = (0, workflow_labels_1.resolveWorkflowStateSyncLabelNames)(syncInput);
        await this.patchIssueLike(input.issueId, { labelNames: nextLabels });
    }
    async ensureWorkflowSchema(options) {
        const schemaChanges = await this.listWorkflowSchemaChanges();
        for (const requirement of schemaChanges.missing) {
            await this.requestJson("POST", `${this.projectApiBaseUrl}/labels`, {
                name: requirement.name,
                color: formatGitLabLabelColor(requirement.color),
                description: requirement.description,
            });
        }
        if (options?.syncMetadata !== false) {
            for (const requirement of schemaChanges.mismatched) {
                await this.requestJson("PUT", `${this.projectApiBaseUrl}/labels/${encodeURIComponent(requirement.name)}`, {
                    new_name: requirement.name,
                    color: formatGitLabLabelColor(requirement.color),
                    description: requirement.description,
                });
            }
        }
        return [
            ...schemaChanges.missing.map((requirement) => ({
                ...requirement,
                kind: "missing",
            })),
            ...(options?.syncMetadata === false
                ? []
                : schemaChanges.mismatched.map((requirement) => ({
                    ...requirement,
                    kind: "metadata",
                }))),
        ];
    }
    async checkWorkflowSchema() {
        const schemaChanges = await this.listWorkflowSchemaChanges();
        const missingRequirements = [
            ...schemaChanges.missing.map((requirement) => ({
                ...requirement,
                kind: "missing",
            })),
            ...schemaChanges.mismatched.map((requirement) => ({
                ...requirement,
                kind: "metadata",
            })),
        ];
        return {
            ok: missingRequirements.length === 0,
            missingRequirements,
        };
    }
    async listIssueRecordsOnly(query) {
        const url = new URL(`${this.projectApiBaseUrl}/issues`);
        applyIssueListQuery(url, query);
        const issues = await this.collectPaginatedArray(url.toString());
        return issues.map((issue) => this.mapIssueRecord(issue));
    }
    async listMergeRequestRecords(query) {
        const mergeRequests = (await Promise.all(resolveGitLabMergeRequestApiStates(query.state).map((state) => {
            const url = new URL(`${this.projectApiBaseUrl}/merge_requests`);
            applyMergeRequestListQuery(url, query, state);
            return this.collectPaginatedArray(url.toString());
        }))).flat();
        return mergeRequests.map((mergeRequest) => this.mapMergeRequestRecord(mergeRequest, query.includeSourceBranchName));
    }
    async searchIssueRecordsOnly(query, search) {
        const url = new URL(`${this.projectApiBaseUrl}/issues`);
        applySearchQuery(url, query, search.searchTerm);
        return (await this.collectPaginatedArray(url.toString(), {
            resultLimit: search.limit,
            acceptItem: (issue) => !(0, issue_search_1.shouldFilterIssueSearchTitle)(search) ||
                (0, issue_search_1.matchesIssueSearchTitle)(issue.title, search),
        })).map((issue) => this.mapIssueRecord(issue));
    }
    async searchMergeRequestRecords(query, search) {
        const mergeRequests = (await Promise.all(resolveGitLabMergeRequestApiStates(query.state).map((state) => {
            const url = new URL(`${this.projectApiBaseUrl}/merge_requests`);
            applySearchQuery(url, query, search.searchTerm, state);
            return this.collectPaginatedArray(url.toString(), {
                resultLimit: search.limit,
                acceptItem: (mergeRequest) => !(0, issue_search_1.shouldFilterIssueSearchTitle)(search) ||
                    (0, issue_search_1.matchesIssueSearchTitle)(mergeRequest.title, search),
            });
        }))).flat();
        return mergeRequests.map((mergeRequest) => this.mapMergeRequestRecord(mergeRequest, true));
    }
    async getIssueRecordByIid(iid) {
        return this.mapIssueRecord(await this.requestJson("GET", `${this.projectApiBaseUrl}/issues/${iid}`));
    }
    async getMergeRequestRecord(iid) {
        return this.mapMergeRequestRecord(await this.requestJson("GET", `${this.projectApiBaseUrl}/merge_requests/${iid}`), true);
    }
    async patchIssueLike(issueId, input) {
        assertGitLabProjectBindingSupported(input.projectRef);
        const body = {};
        if (input.title !== undefined && input.title !== null) {
            body.title = input.title;
        }
        if (input.body !== undefined && input.body !== null) {
            body.description = input.body;
        }
        if (input.labelNames !== undefined) {
            body.labels = input.labelNames.join(",");
        }
        if (input.assignees !== undefined) {
            body.assignee_ids = await (0, api_support_1.resolveGitLabUserIds)({
                projectRef: this.config.projectRef,
                token: this.token,
                errorName: GITLAB_TICKET_TRACKER_ERROR_NAME,
                logins: input.assignees,
            });
        }
        if (input.milestoneRef !== undefined && input.milestoneRef !== null) {
            const milestoneId = await this.resolveMilestoneId(input.milestoneRef);
            if (milestoneId !== null) {
                body.milestone_id = milestoneId;
            }
        }
        if (isGitLabMergeRequestTrackerId(issueId)) {
            return this.mapMergeRequestRecord(await this.requestJson("PUT", `${this.projectApiBaseUrl}/merge_requests/${parseGitLabMergeRequestTrackerId(issueId)}`, body), true);
        }
        return this.mapIssueRecord(await this.requestJson("PUT", `${this.projectApiBaseUrl}/issues/${parseGitLabIssueTrackerId(issueId)}`, body));
    }
    async mapIssue(issue) {
        return (0, issue_normalization_1.normalizeMappedTrackerIssueRecord)({
            issueRecord: this.mapIssueRecord(issue),
            requireWorkflowState: true,
            parseWorkflowState: (labelNames) => this.workflowStateStore.parse(labelNames),
        });
    }
    mapIssueRecord(issue) {
        return (0, issue_normalization_1.normalizeTrackerIssueRecord)({
            id: String(issue.iid),
            displayId: issue.references?.short ?? `#${issue.iid}`,
            title: issue.title,
            body: issue.description ?? "",
            url: issue.web_url,
            author: issue.author?.username ?? null,
            kind: types_1.TRACKER_ITEM_KINDS.issue,
            labelNames: issue.labels ?? [],
            assignees: mapUsernames(issue.assignees),
            isOpen: issue.state === "opened",
            updatedAt: issue.updated_at,
            completedAt: issue.closed_at ?? null,
            ...(issue.milestone?.id !== undefined && issue.milestone.id !== null
                ? { milestoneRef: String(issue.milestone.id) }
                : issue.milestone?.iid !== undefined && issue.milestone.iid !== null
                    ? { milestoneRef: String(issue.milestone.iid) }
                    : {}),
        });
    }
    mapMergeRequestRecord(mergeRequest, includeSourceBranchName) {
        return (0, issue_normalization_1.normalizeTrackerIssueRecord)({
            id: formatGitLabMergeRequestTrackerId(mergeRequest.iid),
            displayId: mergeRequest.references?.short ?? `!${mergeRequest.iid}`,
            pullRequestNumber: mergeRequest.iid,
            title: mergeRequest.title,
            body: mergeRequest.description ?? "",
            url: mergeRequest.web_url,
            author: mergeRequest.author?.username ?? null,
            kind: types_1.TRACKER_ITEM_KINDS.pullRequest,
            labelNames: mergeRequest.labels ?? [],
            assignees: mapUsernames(mergeRequest.assignees),
            isOpen: mergeRequest.state === "opened",
            updatedAt: mergeRequest.updated_at,
            completedAt: mergeRequest.merged_at ?? mergeRequest.closed_at ?? null,
            ...(includeSourceBranchName && mergeRequest.source_branch
                ? { sourceBranchName: mergeRequest.source_branch }
                : {}),
            ...(mergeRequest.milestone?.id !== undefined &&
                mergeRequest.milestone.id !== null
                ? { milestoneRef: String(mergeRequest.milestone.id) }
                : mergeRequest.milestone?.iid !== undefined &&
                    mergeRequest.milestone.iid !== null
                    ? { milestoneRef: String(mergeRequest.milestone.iid) }
                    : {}),
        });
    }
    async resolveMilestoneId(milestoneRef) {
        const normalized = milestoneRef?.trim();
        if (!normalized) {
            return null;
        }
        const milestones = await this.collectPaginatedArray(`${this.projectApiBaseUrl}/milestones?state=all`);
        const milestone = milestones.find((entry) => entry.title === normalized ||
            String(entry.id ?? "") === normalized ||
            String(entry.iid ?? "") === normalized);
        return milestone?.id ?? null;
    }
    async getProjectId() {
        return (await this.requestJson("GET", this.projectApiBaseUrl)).id;
    }
    async resolveCommentTargetFromParsedId(target) {
        const issueId = target.kind === "mr"
            ? formatGitLabMergeRequestTrackerId(target.iid)
            : String(target.iid);
        const issue = await this.getIssueRecord(issueId);
        return {
            ...target,
            displayId: issue.displayId,
            targetUrl: issue.url,
        };
    }
    async listMergeRequestDiscussionComments(target) {
        const discussions = await this.collectPaginatedArray(`${this.projectApiBaseUrl}/merge_requests/${target.iid}/discussions`);
        const commentsById = new Map();
        for (const discussion of discussions) {
            for (const note of discussion.notes ?? []) {
                if (note.system === true) {
                    continue;
                }
                commentsById.set(note.id, mapNote(note, target));
            }
        }
        return [...commentsById.values()].sort(comment_support_1.compareTrackerComments);
    }
    async listWorkflowSchemaChanges() {
        const labels = await this.collectPaginatedArray(`${this.projectApiBaseUrl}/labels`);
        return (0, workflow_labels_1.diffWorkflowLabelDefinitions)(this.requiredWorkflowLabels, labels);
    }
    async collectPaginatedArray(url, options = {}) {
        const results = [];
        const resultLimit = options.resultLimit ?? null;
        if (resultLimit !== null && resultLimit <= 0) {
            return results;
        }
        let nextUrl = url;
        while (nextUrl !== null) {
            const response = await this.request("GET", nextUrl);
            const items = (await response.json());
            for (const item of items) {
                if (options.acceptItem !== undefined && !options.acceptItem(item)) {
                    continue;
                }
                results.push(item);
                if (resultLimit !== null && results.length >= resultLimit) {
                    return results;
                }
            }
            const nextPage = response.headers.get("x-next-page");
            if (!nextPage) {
                nextUrl = null;
            }
            else {
                const current = new URL(nextUrl);
                current.searchParams.set("page", nextPage);
                nextUrl = current.toString();
            }
        }
        return results;
    }
    async requestJson(method, url, body) {
        const response = await this.request(method, url, body);
        if (response.status === 204) {
            return undefined;
        }
        return (await response.json());
    }
    async request(method, url, body) {
        return (0, api_support_1.requestGitLab)({
            method,
            url,
            token: this.token,
            errorName: GITLAB_TICKET_TRACKER_ERROR_NAME,
            body,
        });
    }
}
exports.GitLabTicketTrackerClient = GitLabTicketTrackerClient;
function applyIssueListQuery(url, query) {
    url.searchParams.set("per_page", String(types_1.TRACKER_PROVIDER_REST_PAGE_LIMIT));
    if (query.state && query.state !== "all") {
        url.searchParams.set("state", query.state === "open" ? "opened" : "closed");
    }
    if (query.since) {
        url.searchParams.set("updated_after", query.since);
    }
    if (query.labelNames && query.labelNames.length > 0) {
        url.searchParams.set("labels", query.labelNames.join(","));
    }
}
function applySearchQuery(url, query, searchTerm, mergeRequestState) {
    url.searchParams.set("per_page", String(types_1.TRACKER_PROVIDER_REST_PAGE_LIMIT));
    url.searchParams.set("search", searchTerm);
    url.searchParams.set("order_by", "updated_at");
    url.searchParams.set("sort", "desc");
    const state = mergeRequestState ?? query.state;
    if (state && state !== "all") {
        url.searchParams.set("state", state === "open" ? "opened" : state);
    }
}
function resolveGitLabMergeRequestApiStates(state) {
    if (state === "closed") {
        return ["closed", "merged"];
    }
    if (state === "open") {
        return ["open"];
    }
    return ["all"];
}
function applyMergeRequestListQuery(url, query, state) {
    url.searchParams.set("per_page", String(types_1.TRACKER_PROVIDER_REST_PAGE_LIMIT));
    if (state !== "all") {
        url.searchParams.set("state", state === "open" ? "opened" : state);
    }
    if (query.since) {
        url.searchParams.set("updated_after", query.since);
    }
    if (query.labelNames && query.labelNames.length > 0) {
        url.searchParams.set("labels", query.labelNames.join(","));
    }
}
function formatGitLabLabelColor(color) {
    return color.startsWith("#") ? color : `#${color}`;
}
function mapIssueDependency(issue) {
    const dependency = {
        id: String(issue.iid),
        displayId: issue.references?.short ?? `#${issue.iid}`,
        title: issue.title,
        url: issue.web_url,
        isOpen: issue.state === "opened",
    };
    if (issue.updated_at !== undefined && issue.updated_at !== null) {
        dependency.updatedAt = issue.updated_at;
    }
    return dependency;
}
function resolveLinkedIssue(link) {
    const linkedIssue = link.target_issue ?? link.source_issue ?? null;
    if (linkedIssue) {
        return linkedIssue;
    }
    if (link.iid === undefined ||
        link.iid === null ||
        link.title === undefined ||
        link.title === null ||
        link.web_url === undefined ||
        link.web_url === null ||
        link.state === undefined ||
        link.state === null ||
        link.updated_at === undefined ||
        link.updated_at === null) {
        return null;
    }
    return {
        iid: link.iid,
        title: link.title,
        web_url: link.web_url,
        state: link.state,
        updated_at: link.updated_at,
        ...(link.description !== undefined
            ? { description: link.description }
            : {}),
        ...(link.labels !== undefined ? { labels: link.labels } : {}),
        ...(link.assignees !== undefined ? { assignees: link.assignees } : {}),
        ...(link.author !== undefined ? { author: link.author } : {}),
        ...(link.references !== undefined ? { references: link.references } : {}),
        ...(link.milestone !== undefined ? { milestone: link.milestone } : {}),
    };
}
function mapNote(note, target) {
    const isReviewComment = target.targetKind === types_1.TRACKER_ITEM_KINDS.pullRequest &&
        (note.type === "DiffNote" ||
            note.type === "DiscussionNote" ||
            note.position !== undefined);
    const position = note.position ?? null;
    return {
        id: formatGitLabCommentId(target, note.id),
        url: `${target.targetUrl}#note_${note.id}`,
        body: note.body,
        author: note.author?.username ?? "",
        ...(note.author?.bot === true ? { isBot: true } : {}),
        ...(note.system === true ? { isSystem: true } : {}),
        createdAt: note.created_at,
        updatedAt: note.updated_at ?? null,
        kind: isReviewComment
            ? types_1.TRACKER_COMMENT_KINDS.pullRequestReviewComment
            : types_1.TRACKER_COMMENT_KINDS.issueComment,
        targetKind: target.targetKind,
        targetDisplayId: target.displayId,
        targetUrl: target.targetUrl,
        ...(target.targetKind === types_1.TRACKER_ITEM_KINDS.pullRequest
            ? {
                pullRequestNumber: target.iid,
                pullRequestUrl: target.targetUrl,
            }
            : {}),
        ...(isReviewComment
            ? {
                reviewState: note.resolved === true
                    ? "RESOLVED"
                    : note.resolvable === true
                        ? "COMMENTED"
                        : null,
                path: position?.new_path ?? position?.old_path ?? null,
                line: position?.new_line ?? position?.old_line ?? null,
                side: position?.new_line !== undefined && position?.new_line !== null
                    ? "RIGHT"
                    : position?.old_line !== undefined && position?.old_line !== null
                        ? "LEFT"
                        : null,
                commitId: position?.head_sha ?? null,
            }
            : {}),
    };
}
function buildGitLabProjectEventsUrl(projectApiBaseUrl) {
    const url = new URL(`${projectApiBaseUrl}/events`);
    url.searchParams.set("action", "commented");
    url.searchParams.set("sort", "desc");
    url.searchParams.set("per_page", String(types_1.TRACKER_PROVIDER_REST_PAGE_LIMIT));
    return url.toString();
}
function resolveGitLabConfigScanActivityIssueId(event) {
    const note = event.note;
    if (!note || note.system === true || !isGitLabCommentEvent(event)) {
        return null;
    }
    const noteableIid = note.noteable_iid;
    if (typeof noteableIid !== "number" || !Number.isFinite(noteableIid)) {
        return null;
    }
    if (note.noteable_type === "Issue") {
        return String(noteableIid);
    }
    if (note.noteable_type === "MergeRequest") {
        return formatGitLabMergeRequestTrackerId(noteableIid);
    }
    return null;
}
function isGitLabCommentEvent(event) {
    return (event.target_type === "Note" ||
        event.target_type === "note" ||
        event.action_name === "commented on");
}
function compareGitLabEventIds(left, right) {
    const leftNumber = Number(left);
    const rightNumber = Number(right);
    if (Number.isFinite(leftNumber) && Number.isFinite(rightNumber)) {
        return leftNumber - rightNumber;
    }
    return left.localeCompare(right);
}
function resolveGitLabCommentTarget(issueId, issue) {
    if (isGitLabMergeRequestTrackerId(issueId)) {
        const iid = parseGitLabMergeRequestTrackerId(issueId);
        return {
            kind: "mr",
            apiSegment: "merge_requests",
            iid,
            targetKind: types_1.TRACKER_ITEM_KINDS.pullRequest,
            displayId: issue.displayId,
            targetUrl: issue.url,
        };
    }
    const iid = parseGitLabIssueTrackerId(issueId);
    return {
        kind: "issue",
        apiSegment: "issues",
        iid,
        targetKind: types_1.TRACKER_ITEM_KINDS.issue,
        displayId: issue.displayId,
        targetUrl: issue.url,
    };
}
function parseGitLabCommentId(commentId) {
    const match = /^(issue|mr):(\d+):note:(\d+)$/u.exec(commentId);
    if (!match) {
        throw new Error(`Invalid GitLab comment id: ${commentId}`);
    }
    const [, kind, rawIid, rawNoteId] = match;
    const iid = Number(rawIid);
    const noteId = Number(rawNoteId);
    if (kind === "mr") {
        return {
            kind,
            apiSegment: "merge_requests",
            iid,
            noteId,
            targetKind: types_1.TRACKER_ITEM_KINDS.pullRequest,
            displayId: `!${iid}`,
            targetUrl: "",
        };
    }
    return {
        kind: "issue",
        apiSegment: "issues",
        iid,
        noteId,
        targetKind: types_1.TRACKER_ITEM_KINDS.issue,
        displayId: `#${iid}`,
        targetUrl: "",
    };
}
function formatGitLabCommentId(target, noteId) {
    return `${target.kind}:${target.iid}:note:${noteId}`;
}
function formatGitLabMergeRequestTrackerId(iid) {
    return `mr:${iid}`;
}
function isGitLabMergeRequestTrackerId(issueId) {
    return /^mr:\d+$/u.test(issueId);
}
function parseGitLabMergeRequestTrackerId(issueId) {
    if (!isGitLabMergeRequestTrackerId(issueId)) {
        throw new Error(`Invalid GitLab merge request tracker id: ${issueId}`);
    }
    return Number(issueId.slice("mr:".length));
}
function parseGitLabIssueTrackerId(issueId) {
    if (!/^\d+$/u.test(issueId)) {
        throw new Error(`Invalid GitLab issue tracker id: ${issueId}`);
    }
    return Number(issueId);
}
function assertGitLabProjectBindingSupported(projectRef) {
    if (projectRef?.trim()) {
        throw new Error("GitLab ticket tracker projectRef binding is not supported.");
    }
}
function mapUsernames(users) {
    return (users ?? []).flatMap((user) => user.username ? [user.username] : []);
}
