"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.listGiteaComments = listGiteaComments;
exports.acknowledgeGiteaComment = acknowledgeGiteaComment;
exports.createGiteaTrackerComment = createGiteaTrackerComment;
exports.updateGiteaTrackerComment = updateGiteaTrackerComment;
exports.syncGiteaWorkflowState = syncGiteaWorkflowState;
exports.ensureGiteaWorkflowSchema = ensureGiteaWorkflowSchema;
exports.checkGiteaWorkflowSchema = checkGiteaWorkflowSchema;
exports.parseGiteaTotalCount = parseGiteaTotalCount;
exports.isPullRequestIssue = isPullRequestIssue;
exports.resolveIssueNumber = resolveIssueNumber;
exports.extractLabelNames = extractLabelNames;
exports.extractAssigneeLogins = extractAssigneeLogins;
exports.collectDependencyList = collectDependencyList;
exports.resolveString = resolveString;
exports.isNotFoundError = isNotFoundError;
exports.isAlreadyMergedGiteaError = isAlreadyMergedGiteaError;
exports.createGiteaApiError = createGiteaApiError;
exports.formatGiteaLabelColor = formatGiteaLabelColor;
exports.collectPaginatedGiteaArray = collectPaginatedGiteaArray;
const catalog_1 = require("../../i18n/catalog");
const comment_reactions_1 = require("./comment-reactions");
const comment_support_1 = require("./comment-support");
const types_1 = require("./types");
const workflow_labels_1 = require("./workflow-labels");
const GITEA_ALREADY_MERGED_MESSAGE = /\balready(?:\s+been)?\s+merged\b/i;
async function listGiteaComments(input) {
    const timeline = input.options.timeline ?? "full";
    const issue = await input.resolveIssueByNumber(Number(input.issueId));
    const issueNumber = resolveIssueNumber(issue) ?? Number(input.issueId);
    const issueComments = await input.collectPaginatedArray(`${input.repoApiBaseUrl}/issues/${issueNumber}/comments`);
    const timelineComments = issueComments.map((comment) => mapIssueComment(comment, issue, input.locale));
    if (timeline !== "issue-only" && isPullRequestIssue(issue)) {
        const reviews = await input.collectPaginatedArray(`${input.repoApiBaseUrl}/pulls/${issueNumber}/reviews`);
        const reviewComments = (await Promise.all(reviews.map((review) => input.requestJson("GET", `${input.repoApiBaseUrl}/pulls/${issueNumber}/reviews/${review.id}/comments`)))).flat();
        const reviewStateById = new Map();
        for (const review of reviews) {
            reviewStateById.set(review.id, review.state);
        }
        timelineComments.push(...reviews.map((review) => mapPullRequestReview(review, issue, input.locale)), ...reviewComments.map((comment) => mapPullRequestReviewComment(comment, issue, reviewStateById, input.locale)));
    }
    return timelineComments.sort(comment_support_1.compareTrackerComments);
}
async function acknowledgeGiteaComment(input) {
    if (input.comment.kind !== types_1.TRACKER_ACKNOWLEDGEABLE_COMMENT_KINDS.issueComment) {
        return;
    }
    const reaction = (0, comment_reactions_1.selectCommentReaction)(input.comment.id, input.commentReactions);
    if (reaction === null) {
        return;
    }
    await input.requestJson("POST", `${input.repoApiBaseUrl}/issues/comments/${input.comment.id}/reactions`, {
        content: reaction,
    });
}
async function createGiteaTrackerComment(input) {
    const response = await input.requestFormUrlEncoded("POST", `${input.repoApiBaseUrl}/issues/${input.issueId}/comments`, {
        body: input.body,
    });
    return mapComment(response, Number(input.issueId), input.locale);
}
async function updateGiteaTrackerComment(input) {
    const response = await input.requestFormUrlEncoded("PATCH", `${input.repoApiBaseUrl}/issues/comments/${input.commentId}`, {
        body: input.body,
    });
    const issueNumber = (0, comment_support_1.resolveTrackerIssueNumberFromApiUrl)(response.issue_url) ??
        (0, comment_support_1.resolveTrackerIssueNumberFromApiUrl)(response.pull_request_url) ??
        (0, comment_support_1.resolveTrackerIssueNumberFromApiUrl)(response.html_url);
    if (issueNumber === null) {
        throw new Error("Updated comment issue URL is unavailable.");
    }
    return mapComment(response, issueNumber, input.locale);
}
async function syncGiteaWorkflowState(input) {
    const currentLabels = input.currentLabelNames ??
        extractLabelNames((await input.resolveIssueByNumber(Number(input.issueId))).labels);
    if ((0, workflow_labels_1.isWorkflowStateSyncNoop)({
        workflowLabels: input.resolvedWorkflowLabels,
        currentLabelNames: currentLabels,
        next: input.next,
        ...(input.labelProjection !== undefined
            ? { labelProjection: input.labelProjection }
            : {}),
        ...(input.auxiliaryManagedLabels !== undefined
            ? { auxiliaryManagedLabels: input.auxiliaryManagedLabels }
            : {}),
    })) {
        return;
    }
    const nextLabels = (0, workflow_labels_1.resolveWorkflowStateSyncLabelNames)({
        workflowLabels: input.resolvedWorkflowLabels,
        currentLabelNames: currentLabels,
        next: input.next,
        ...(input.labelProjection !== undefined
            ? { labelProjection: input.labelProjection }
            : {}),
        ...(input.auxiliaryManagedLabels !== undefined
            ? { auxiliaryManagedLabels: input.auxiliaryManagedLabels }
            : {}),
    });
    await input.requestJson("PUT", `${input.repoApiBaseUrl}/issues/${input.issueId}/labels`, { labels: nextLabels });
    await verifyGiteaSyncedLabels({
        issueId: input.issueId,
        expectedLabels: nextLabels,
        resolveIssueByNumber: input.resolveIssueByNumber,
    });
}
async function ensureGiteaWorkflowSchema(input) {
    const schemaChanges = await listGiteaWorkflowSchemaChanges(input);
    for (const label of schemaChanges.missing) {
        await input.requestJson("POST", `${input.repoApiBaseUrl}/labels`, {
            name: label.name,
            color: formatGiteaLabelColor(label.color),
            description: label.description,
        });
    }
    if (input.syncMetadata !== false) {
        for (const change of schemaChanges.mismatched) {
            await input.requestJson("PATCH", `${input.repoApiBaseUrl}/labels/${change.current.id}`, {
                name: change.required.name,
                color: formatGiteaLabelColor(change.required.color),
                description: change.required.description,
            });
        }
    }
    return [
        ...schemaChanges.missing.map((requirement) => ({
            ...requirement,
            kind: "missing",
        })),
        ...(input.syncMetadata === false
            ? []
            : schemaChanges.mismatched.map((change) => ({
                ...change.required,
                kind: "metadata",
            }))),
    ];
}
async function checkGiteaWorkflowSchema(input) {
    const schemaChanges = await listGiteaWorkflowSchemaChanges(input);
    const missingRequirements = [
        ...schemaChanges.missing.map((requirement) => ({
            ...requirement,
            kind: "missing",
        })),
        ...schemaChanges.mismatched.map((change) => ({
            ...change.required,
            kind: "metadata",
        })),
    ];
    return {
        ok: missingRequirements.length === 0,
        missingRequirements,
    };
}
async function listGiteaWorkflowSchemaChanges(input) {
    const labels = await input.collectPaginatedArray(`${input.repoApiBaseUrl}/labels`);
    const schemaDiff = (0, workflow_labels_1.diffWorkflowLabelDefinitions)(input.requiredWorkflowLabels, labels);
    const labelByName = new Map(labels.map((label) => [label.name, label]));
    return {
        missing: schemaDiff.missing,
        mismatched: schemaDiff.mismatched.flatMap((required) => {
            const current = labelByName.get(required.name);
            return current ? [{ required, current }] : [];
        }),
    };
}
async function verifyGiteaSyncedLabels(input) {
    let actualLabels = [];
    for (let attempt = 0; attempt < 2; attempt += 1) {
        actualLabels = extractLabelNames((await input.resolveIssueByNumber(Number(input.issueId))).labels);
        if (sameLabelSet(actualLabels, input.expectedLabels)) {
            return;
        }
    }
    throw new Error(`Gitea workflow label sync verification failed for issue #${input.issueId}. Expected labels: ${formatLabelList(input.expectedLabels)}. Actual labels: ${formatLabelList(actualLabels)}. This may indicate a concurrent label update or an inconsistent Gitea response.`);
}
function mapIssueComment(comment, issue, locale) {
    const target = (0, comment_support_1.buildTrackerCommentTarget)({
        issueNumber: resolveIssueNumber(issue),
        issueUrl: issue.html_url,
        isPullRequest: isPullRequestIssue(issue),
    });
    return {
        id: String(comment.id),
        url: comment.html_url,
        body: comment.body ?? "",
        author: comment.user?.login ??
            comment.user?.username ??
            comment.original_author ??
            (0, catalog_1.translate)(locale, "common.placeholder.unknown"),
        ...(comment.user?.is_bot === true ? { isBot: true } : {}),
        createdAt: comment.created_at,
        updatedAt: comment.updated_at ?? null,
        kind: types_1.TRACKER_COMMENT_KINDS.issueComment,
        ...target,
    };
}
function parseGiteaTotalCount(headers) {
    const rawValue = headers.get("x-total-count");
    if (rawValue === null) {
        return null;
    }
    const parsed = Number(rawValue);
    return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : null;
}
function mapComment(comment, issueNumber, locale) {
    return {
        id: String(comment.id),
        url: comment.html_url,
        body: comment.body ?? "",
        author: comment.user?.login ??
            comment.user?.username ??
            comment.original_author ??
            (0, catalog_1.translate)(locale, "common.placeholder.unknown"),
        ...(comment.user?.is_bot === true ? { isBot: true } : {}),
        createdAt: comment.created_at,
        updatedAt: comment.updated_at ?? null,
        kind: types_1.TRACKER_COMMENT_KINDS.issueComment,
        ...(0, comment_support_1.buildTrackerCommentTargetFromCommentUrl)({
            issueNumber,
            commentUrl: comment.html_url,
            isPullRequest: comment.pull_request_url != null,
        }),
    };
}
function mapPullRequestReview(review, issue, locale) {
    const target = (0, comment_support_1.buildTrackerCommentTarget)({
        issueNumber: resolveIssueNumber(issue),
        issueUrl: issue.html_url,
        isPullRequest: isPullRequestIssue(issue),
    });
    return {
        id: String(review.id),
        url: review.html_url,
        body: review.body ?? "",
        author: review.user?.login ??
            review.user?.username ??
            review.original_author ??
            (0, catalog_1.translate)(locale, "common.placeholder.unknown"),
        ...(review.user?.is_bot === true ? { isBot: true } : {}),
        createdAt: review.submitted_at ?? issue.updated_at,
        updatedAt: review.submitted_at ?? issue.updated_at,
        kind: types_1.TRACKER_COMMENT_KINDS.pullRequestReview,
        ...target,
        reviewState: review.state,
        pullRequestReviewId: review.id,
    };
}
function mapPullRequestReviewComment(comment, issue, reviewStateById, locale) {
    const reviewId = comment.pull_request_review_id ?? null;
    const reviewState = reviewId === null ? null : (reviewStateById.get(reviewId) ?? null);
    const target = (0, comment_support_1.buildTrackerCommentTarget)({
        issueNumber: resolveIssueNumber(issue),
        issueUrl: issue.html_url,
        isPullRequest: isPullRequestIssue(issue),
    });
    return {
        id: String(comment.id),
        url: comment.html_url,
        body: comment.body ?? "",
        author: comment.user?.login ??
            comment.user?.username ??
            comment.original_author ??
            (0, catalog_1.translate)(locale, "common.placeholder.unknown"),
        ...(comment.user?.is_bot === true ? { isBot: true } : {}),
        createdAt: comment.created_at,
        updatedAt: comment.updated_at ?? null,
        kind: types_1.TRACKER_COMMENT_KINDS.pullRequestReviewComment,
        ...target,
        reviewState,
        ...(reviewId === null ? {} : { pullRequestReviewId: reviewId }),
        path: comment.path ?? null,
        line: comment.line ?? null,
        side: comment.side ?? null,
        commitId: comment.commit_id ?? comment.original_commit_id ?? null,
        inReplyToCommentId: comment.in_reply_to_id ?? null,
    };
}
function isPullRequestIssue(issue) {
    return issue.pull_request !== undefined && issue.pull_request !== null;
}
function resolveIssueNumber(issue) {
    return issue.number ?? issue.index ?? null;
}
function extractLabelNames(labels) {
    return (labels ?? []).flatMap((label) => {
        if (typeof label === "string") {
            return [label];
        }
        return typeof label.name === "string" ? [label.name] : [];
    });
}
function extractAssigneeLogins(issue) {
    const assignees = issue.assignees ?? [];
    const logins = assignees.flatMap((assignee) => {
        const login = assignee.login ?? assignee.username ?? null;
        return login ? [login] : [];
    });
    const primaryAssignee = issue.assignee?.login ?? issue.assignee?.username ?? null;
    if (primaryAssignee) {
        logins.push(primaryAssignee);
    }
    return [...new Set(logins.filter((login) => login.length > 0))];
}
function sameLabelSet(left, right) {
    if (left.length !== right.length) {
        return false;
    }
    const leftCounts = countLabels(left);
    const rightCounts = countLabels(right);
    if (leftCounts.size !== rightCounts.size) {
        return false;
    }
    for (const [label, count] of leftCounts.entries()) {
        if (rightCounts.get(label) !== count) {
            return false;
        }
    }
    return true;
}
function countLabels(labels) {
    const counts = new Map();
    for (const label of labels) {
        counts.set(label, (counts.get(label) ?? 0) + 1);
    }
    return counts;
}
function formatLabelList(labels) {
    return labels.length === 0 ? "(none)" : labels.join(", ");
}
function collectDependencyList(response) {
    if (response && typeof response === "object") {
        const blockedBy = response.blocked_by;
        if (Array.isArray(blockedBy)) {
            return blockedBy;
        }
        const dependencies = response.dependencies;
        if (Array.isArray(dependencies)) {
            return dependencies;
        }
    }
    return [];
}
function resolveString(value) {
    return typeof value === "string" ? value : null;
}
function isNotFoundError(error) {
    return (typeof error === "object" &&
        error !== null &&
        "status" in error &&
        error.status === 404);
}
function isAlreadyMergedGiteaError(error) {
    const message = error instanceof Error ? error.message : String(error);
    return GITEA_ALREADY_MERGED_MESSAGE.test(message);
}
async function createGiteaApiError(response) {
    const text = await response.text().catch(() => "");
    let message = `Gitea API request failed (${response.status} ${response.statusText})`;
    if (text.trim().length > 0) {
        try {
            const parsed = JSON.parse(text);
            if (typeof parsed.message === "string" &&
                parsed.message.trim().length > 0) {
                message = parsed.message;
            }
            else {
                message = `${message}: ${text}`;
            }
        }
        catch {
            message = `${message}: ${text}`;
        }
    }
    const error = new Error(message);
    Object.assign(error, { status: response.status });
    return error;
}
function formatGiteaLabelColor(color) {
    return color.startsWith("#") ? color : `#${color}`;
}
async function collectPaginatedGiteaArray(input) {
    const results = [];
    const resultLimit = input.resultLimit ?? null;
    if (resultLimit !== null && resultLimit <= 0) {
        return results;
    }
    const seenPageSignatures = new Set();
    for (let page = 1;; page += 1) {
        const pageUrl = new URL(input.url);
        pageUrl.searchParams.set("page", String(page));
        pageUrl.searchParams.set("limit", String(types_1.TRACKER_PROVIDER_REST_PAGE_LIMIT));
        for (const [key, value] of Object.entries(input.query ?? {})) {
            if (value !== undefined && value !== "") {
                pageUrl.searchParams.set(key, value);
            }
        }
        const { data: items, headers } = await input.requestJsonWithHeaders("GET", pageUrl.toString());
        const pageSignature = JSON.stringify(items);
        if (seenPageSignatures.has(pageSignature)) {
            return results;
        }
        seenPageSignatures.add(pageSignature);
        for (const item of items) {
            if (input.acceptItem !== undefined && !input.acceptItem(item)) {
                continue;
            }
            results.push(item);
            if (resultLimit !== null && results.length >= resultLimit) {
                return results;
            }
        }
        const totalCount = parseGiteaTotalCount(headers);
        if (items.length < types_1.TRACKER_PROVIDER_REST_PAGE_LIMIT ||
            (totalCount !== null && results.length >= totalCount)) {
            return results;
        }
    }
}
