"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.listGitHubTrackerIssues = listGitHubTrackerIssues;
exports.listGitHubServiceManagedDeltaIssues = listGitHubServiceManagedDeltaIssues;
exports.listGitHubComments = listGitHubComments;
exports.createGitHubTrackerComment = createGitHubTrackerComment;
exports.acknowledgeGitHubComment = acknowledgeGitHubComment;
exports.updateGitHubTrackerComment = updateGitHubTrackerComment;
exports.syncGitHubWorkflowState = syncGitHubWorkflowState;
exports.ensureGitHubWorkflowSchema = ensureGitHubWorkflowSchema;
exports.checkGitHubWorkflowSchema = checkGitHubWorkflowSchema;
exports.isNotFoundError = isNotFoundError;
exports.isPullRequestIssue = isPullRequestIssue;
const catalog_1 = require("../../i18n/catalog");
const comment_reactions_1 = require("./comment-reactions");
const comment_support_1 = require("./comment-support");
const issue_normalization_1 = require("./issue-normalization");
const types_1 = require("./types");
const workflow_labels_1 = require("./workflow-labels");
async function listGitHubTrackerIssues(input) {
    const [issues, pullRequests] = await Promise.all([
        listGitHubIssueNodes(input),
        listGitHubPullRequestNodes(input),
    ]);
    return [...issues, ...pullRequests]
        .filter((issue) => isIssueUpdatedSince(issue, input.since))
        .sort((left, right) => right.updated_at.localeCompare(left.updated_at));
}
async function listGitHubServiceManagedDeltaIssues(input) {
    const query = (0, issue_normalization_1.normalizeServiceManagedDeltaIssueListQuery)(input.query);
    const labelNames = new Set(input.labelNames);
    const items = (await listGitHubTrackerIssues({
        octokit: input.octokit,
        owner: input.owner,
        repo: input.repo,
        state: query.state,
        since: query.since,
    })).filter((issue) => issue.labels.some((label) => label.name && labelNames.has(label.name)));
    const mapped = await Promise.all(items.map((issue) => input.mapIssue(issue, {
        requireWorkflowState: false,
        includeSourceBranchName: query.includeSourceBranchName,
    })));
    return (0, issue_normalization_1.collectMappedTrackerIssues)(mapped);
}
const GITHUB_ISSUES_GRAPHQL_QUERY = `query ListAlpacaIssues(
  $owner: String!
  $repo: String!
  $states: [IssueState!]
  $labels: [String!]
  $filterBy: IssueFilters
  $after: String
) {
  repository(owner: $owner, name: $repo) {
    issues(
      first: ${types_1.TRACKER_PROVIDER_GRAPHQL_PAGE_LIMIT}
      after: $after
      states: $states
      labels: $labels
      filterBy: $filterBy
      orderBy: { field: UPDATED_AT, direction: DESC }
    ) {
      pageInfo {
        hasNextPage
        endCursor
      }
      nodes {
        databaseId
        number
        title
        body
        url
        state
        updatedAt
        closedAt
        author {
          login
        }
        labels(first: ${types_1.TRACKER_PROVIDER_GRAPHQL_PAGE_LIMIT}) {
          nodes {
            name
          }
        }
        assignees(first: ${types_1.TRACKER_PROVIDER_GRAPHQL_PAGE_LIMIT}) {
          nodes {
            login
          }
        }
        milestone {
          number
        }
      }
    }
  }
}`;
const GITHUB_PULL_REQUESTS_GRAPHQL_QUERY = `query ListAlpacaPullRequests(
  $owner: String!
  $repo: String!
  $states: [PullRequestState!]
  $labels: [String!]
  $after: String
) {
  repository(owner: $owner, name: $repo) {
    pullRequests(
      first: ${types_1.TRACKER_PROVIDER_GRAPHQL_PAGE_LIMIT}
      after: $after
      states: $states
      labels: $labels
      orderBy: { field: UPDATED_AT, direction: DESC }
    ) {
      pageInfo {
        hasNextPage
        endCursor
      }
      nodes {
        databaseId
        number
        title
        body
        url
        state
        updatedAt
        closedAt
        mergedAt
        author {
          login
        }
        labels(first: ${types_1.TRACKER_PROVIDER_GRAPHQL_PAGE_LIMIT}) {
          nodes {
            name
          }
        }
        assignees(first: ${types_1.TRACKER_PROVIDER_GRAPHQL_PAGE_LIMIT}) {
          nodes {
            login
          }
        }
        milestone {
          number
        }
      }
    }
  }
}`;
async function listGitHubIssueNodes(input) {
    const items = [];
    let after = null;
    while (true) {
        const response = (await input.octokit.graphql(GITHUB_ISSUES_GRAPHQL_QUERY, {
            owner: input.owner,
            repo: input.repo,
            states: resolveGraphqlIssueStates(input.state),
            labels: resolveGraphqlLabels(input.labelNames),
            filterBy: input.since ? { since: input.since } : null,
            after,
        }));
        const connection = response.repository?.issues;
        if (!connection) {
            return items;
        }
        const nodes = (connection.nodes ?? []).flatMap((node) => node ? [node] : []);
        items.push(...nodes.map(mapGraphqlIssueNode));
        if (!connection.pageInfo.hasNextPage) {
            return items;
        }
        after = connection.pageInfo.endCursor;
    }
}
async function listGitHubPullRequestNodes(input) {
    const items = [];
    let after = null;
    while (true) {
        const response = (await input.octokit.graphql(GITHUB_PULL_REQUESTS_GRAPHQL_QUERY, {
            owner: input.owner,
            repo: input.repo,
            states: resolveGraphqlPullRequestStates(input.state),
            labels: resolveGraphqlLabels(input.labelNames),
            after,
        }));
        const connection = response.repository?.pullRequests;
        if (!connection) {
            return items;
        }
        const nodes = (connection.nodes ?? []).flatMap((node) => node ? [node] : []);
        items.push(...nodes.map(mapGraphqlPullRequestNode));
        if (input.since &&
            nodes.length > 0 &&
            !isUpdatedAtOrAfter(nodes[nodes.length - 1]?.updatedAt, input.since)) {
            return items;
        }
        if (!connection.pageInfo.hasNextPage) {
            return items;
        }
        after = connection.pageInfo.endCursor;
    }
}
function resolveGraphqlIssueStates(state) {
    if (state === "open") {
        return ["OPEN"];
    }
    if (state === "closed") {
        return ["CLOSED"];
    }
    return null;
}
function resolveGraphqlPullRequestStates(state) {
    if (state === "open") {
        return ["OPEN"];
    }
    if (state === "closed") {
        return ["CLOSED", "MERGED"];
    }
    return null;
}
function resolveGraphqlLabels(labelNames) {
    return labelNames && labelNames.length > 0 ? [...labelNames] : null;
}
function mapGraphqlIssueNode(node) {
    return {
        id: node.databaseId ?? node.number,
        number: node.number,
        title: node.title,
        body: node.body ?? null,
        html_url: node.url,
        state: node.state === "OPEN" ? "open" : "closed",
        updated_at: node.updatedAt,
        closed_at: node.closedAt ?? null,
        user: node.author?.login ? { login: node.author.login } : null,
        labels: mapGraphqlLabels(node.labels),
        assignees: mapGraphqlAssignees(node.assignees),
        ...(node.milestone?.number
            ? { milestone: { number: node.milestone.number } }
            : {}),
    };
}
function mapGraphqlPullRequestNode(node) {
    return {
        id: node.databaseId ?? node.number,
        number: node.number,
        title: node.title,
        body: node.body ?? null,
        html_url: node.url,
        state: node.state === "OPEN" ? "open" : "closed",
        updated_at: node.updatedAt,
        closed_at: node.mergedAt ?? node.closedAt ?? null,
        user: node.author?.login ? { login: node.author.login } : null,
        labels: mapGraphqlLabels(node.labels),
        assignees: mapGraphqlAssignees(node.assignees),
        pull_request: { url: node.url },
        ...(node.milestone?.number
            ? { milestone: { number: node.milestone.number } }
            : {}),
    };
}
function mapGraphqlLabels(labels) {
    return (labels?.nodes ?? []).flatMap((label) => label ? [{ name: label.name ?? null }] : []);
}
function mapGraphqlAssignees(assignees) {
    return (assignees?.nodes ?? []).flatMap((assignee) => assignee ? [{ login: assignee.login ?? null }] : []);
}
function isIssueUpdatedSince(issue, since) {
    return isUpdatedAtOrAfter(issue.updated_at, since);
}
function isUpdatedAtOrAfter(updatedAt, since) {
    if (!since) {
        return true;
    }
    return Date.parse(updatedAt ?? "") >= Date.parse(since);
}
async function listGitHubComments(input) {
    const timeline = input.options.timeline ?? "full";
    const issue = (await input.octokit.issues.get({
        owner: input.owner,
        repo: input.repo,
        issue_number: Number(input.issueId),
    })).data;
    const comments = (await input.octokit.paginate(input.octokit.issues.listComments, {
        owner: input.owner,
        repo: input.repo,
        issue_number: Number(input.issueId),
        per_page: types_1.TRACKER_PROVIDER_REST_PAGE_LIMIT,
    }));
    const timelineComments = comments.map((comment) => mapIssueComment(comment, issue, input.locale));
    if (timeline !== "issue-only" && isPullRequestIssue(issue)) {
        const pullNumber = Number(issue.number);
        const [reviews, reviewComments] = await Promise.all([
            input.octokit.paginate(input.octokit.pulls.listReviews, {
                owner: input.owner,
                repo: input.repo,
                pull_number: pullNumber,
                per_page: types_1.TRACKER_PROVIDER_REST_PAGE_LIMIT,
            }),
            input.octokit.paginate(input.octokit.pulls.listReviewComments, {
                owner: input.owner,
                repo: input.repo,
                pull_number: pullNumber,
                per_page: types_1.TRACKER_PROVIDER_REST_PAGE_LIMIT,
            }),
        ]);
        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 createGitHubTrackerComment(input) {
    const response = await input.octokit.issues.createComment({
        owner: input.owner,
        repo: input.repo,
        issue_number: Number(input.issueId),
        body: input.body,
    });
    return mapComment(response.data, Number(input.issueId), input.locale);
}
async function acknowledgeGitHubComment(input) {
    if (!isGitHubAcknowledgeableComment(input.comment)) {
        return;
    }
    const reaction = (0, comment_reactions_1.selectCommentReaction)(input.comment.id, input.commentReactions);
    if (reaction === null) {
        return;
    }
    const route = input.comment.kind === types_1.TRACKER_ACKNOWLEDGEABLE_COMMENT_KINDS.issueComment
        ? "POST /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions"
        : "POST /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions";
    await input.octokit.request(route, {
        owner: input.owner,
        repo: input.repo,
        comment_id: Number(input.comment.id),
        content: reaction,
    });
}
async function updateGitHubTrackerComment(input) {
    const response = await input.octokit.issues.updateComment({
        owner: input.owner,
        repo: input.repo,
        comment_id: Number(input.commentId),
        body: input.body,
    });
    const comment = response.data;
    const issueNumber = (0, comment_support_1.resolveTrackerIssueNumberFromApiUrl)(comment.issue_url ?? null) ??
        (0, comment_support_1.resolveTrackerIssueNumberFromApiUrl)(comment.html_url);
    if (issueNumber === null) {
        throw new Error("Updated comment issue URL is unavailable.");
    }
    return mapComment(comment, issueNumber, input.locale);
}
async function syncGitHubWorkflowState(input) {
    const currentLabels = input.currentLabelNames ??
        (await input.octokit.issues.get({
            owner: input.owner,
            repo: input.repo,
            issue_number: Number(input.issueId),
        })).data.labels.flatMap((label) => {
            if (typeof label === "string") {
                return [label];
            }
            return typeof label.name === "string" ? [label.name] : [];
        });
    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.octokit.issues.update({
        owner: input.owner,
        repo: input.repo,
        issue_number: Number(input.issueId),
        labels: nextLabels,
    });
}
async function ensureGitHubWorkflowSchema(input) {
    const schemaChanges = await listGitHubWorkflowSchemaChanges(input);
    for (const label of schemaChanges.missing) {
        await input.octokit.issues.createLabel({
            owner: input.owner,
            repo: input.repo,
            name: label.name,
            color: label.color,
            description: label.description,
        });
    }
    if (input.syncMetadata !== false) {
        for (const label of schemaChanges.mismatched) {
            await input.octokit.issues.updateLabel({
                owner: input.owner,
                repo: input.repo,
                name: label.name,
                color: label.color,
                description: label.description,
            });
        }
    }
    return [
        ...schemaChanges.missing.map((requirement) => ({
            ...requirement,
            kind: "missing",
        })),
        ...(input.syncMetadata === false
            ? []
            : schemaChanges.mismatched.map((requirement) => ({
                ...requirement,
                kind: "metadata",
            }))),
    ];
}
async function checkGitHubWorkflowSchema(input) {
    const schemaChanges = await listGitHubWorkflowSchemaChanges(input);
    const missingRequirements = [
        ...schemaChanges.missing.map((requirement) => ({
            ...requirement,
            kind: "missing",
        })),
        ...schemaChanges.mismatched.map((requirement) => ({
            ...requirement,
            kind: "metadata",
        })),
    ];
    return {
        ok: missingRequirements.length === 0,
        missingRequirements,
    };
}
async function listGitHubWorkflowSchemaChanges(input) {
    const labels = (await input.octokit.paginate(input.octokit.issues.listLabelsForRepo, {
        owner: input.owner,
        repo: input.repo,
        per_page: types_1.TRACKER_PROVIDER_REST_PAGE_LIMIT,
    }));
    return (0, workflow_labels_1.diffWorkflowLabelDefinitions)(input.requiredWorkflowLabels, labels);
}
function isNotFoundError(error) {
    return (typeof error === "object" &&
        error !== null &&
        "status" in error &&
        error.status === 404);
}
function isPullRequestIssue(issue) {
    return issue.pull_request !== undefined && issue.pull_request !== null;
}
function mapIssueComment(comment, issue, locale) {
    const target = (0, comment_support_1.buildTrackerCommentTarget)({
        issueNumber: issue.number,
        issueUrl: issue.html_url,
        isPullRequest: isPullRequestIssue(issue),
    });
    return {
        id: String(comment.id),
        url: comment.html_url,
        body: comment.body ?? "",
        author: comment.user?.login ?? (0, catalog_1.translate)(locale, "common.placeholder.unknown"),
        ...(comment.user?.type === "Bot" ? { isBot: true } : {}),
        createdAt: comment.created_at,
        updatedAt: comment.updated_at ?? null,
        kind: types_1.TRACKER_COMMENT_KINDS.issueComment,
        ...target,
    };
}
function mapComment(comment, issueNumber, locale) {
    return {
        id: String(comment.id),
        url: comment.html_url,
        body: comment.body ?? "",
        author: comment.user?.login ?? (0, catalog_1.translate)(locale, "common.placeholder.unknown"),
        ...(comment.user?.type === "Bot" ? { 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,
        }),
    };
}
function mapPullRequestReview(review, issue, locale) {
    const target = (0, comment_support_1.buildTrackerCommentTarget)({
        issueNumber: issue.number,
        issueUrl: issue.html_url,
        isPullRequest: isPullRequestIssue(issue),
    });
    return {
        id: String(review.id),
        url: review.html_url,
        body: review.body ?? "",
        author: review.user?.login ?? (0, catalog_1.translate)(locale, "common.placeholder.unknown"),
        ...(review.user?.type === "Bot" ? { 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 reviewState = comment.pull_request_review_id !== null &&
        comment.pull_request_review_id !== undefined
        ? (reviewStateById.get(comment.pull_request_review_id) ?? null)
        : null;
    const target = (0, comment_support_1.buildTrackerCommentTarget)({
        issueNumber: issue.number,
        issueUrl: issue.html_url,
        isPullRequest: isPullRequestIssue(issue),
    });
    return {
        id: String(comment.id),
        url: comment.html_url,
        body: comment.body ?? "",
        author: comment.user?.login ?? (0, catalog_1.translate)(locale, "common.placeholder.unknown"),
        ...(comment.user?.type === "Bot" ? { isBot: true } : {}),
        createdAt: comment.created_at,
        updatedAt: comment.updated_at ?? null,
        kind: types_1.TRACKER_COMMENT_KINDS.pullRequestReviewComment,
        ...target,
        reviewState,
        ...(comment.pull_request_review_id !== null &&
            comment.pull_request_review_id !== undefined
            ? {
                pullRequestReviewId: comment.pull_request_review_id,
            }
            : {}),
        path: comment.path ?? null,
        line: comment.line ?? null,
        side: comment.side ?? null,
        commitId: comment.commit_id ?? null,
        inReplyToCommentId: comment.in_reply_to_id ?? null,
    };
}
function isGitHubAcknowledgeableComment(comment) {
    return (comment.kind === types_1.TRACKER_ACKNOWLEDGEABLE_COMMENT_KINDS.issueComment ||
        comment.kind ===
            types_1.TRACKER_ACKNOWLEDGEABLE_COMMENT_KINDS.pullRequestReviewComment);
}
