"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.OctokitGitHubRepositoryHostClient = void 0;
const constants_1 = require("../constants");
const project_ref_1 = require("../github/project-ref");
const constants_2 = require("./constants");
const login_support_1 = require("./login-support");
class OctokitGitHubRepositoryHostClient {
    octokit;
    project;
    constructor(target, octokit) {
        this.octokit = octokit;
        this.project = (0, project_ref_1.parseGitHubProjectRef)(target.projectRef);
    }
    async getRepository() {
        const response = await this.octokit.repos.get({
            owner: this.project.owner,
            repo: this.project.repo,
        });
        return { defaultBranch: response.data.default_branch };
    }
    async findPullRequestByBranch(branchName) {
        const response = await this.octokit.pulls.list({
            owner: this.project.owner,
            repo: this.project.repo,
            state: "all",
            head: `${this.project.owner}:${branchName}`,
            // biome-ignore lint/style/useNamingConvention: GitHub REST parameter name.
            per_page: 1,
        });
        const first = response.data[0];
        return first
            ? mapPullRequest(first)
            : null;
    }
    async getPullRequest(pullRequestNumber) {
        try {
            return mapPullRequest((await this.getPullRequestDetails(pullRequestNumber)));
        }
        catch {
            return null;
        }
    }
    async getPullRequestAutomationState(pullRequestNumber) {
        const response = await this.octokit.graphql(GET_PULL_REQUEST_AUTOMATION_STATE_QUERY, {
            owner: this.project.owner,
            repo: this.project.repo,
            pullRequestNumber,
        });
        const automationState = response.repository.pullRequest;
        if (!automationState || automationState.state !== "OPEN") {
            return null;
        }
        const latestApprovedReviewCommitOid = automationState.reviewDecision === "APPROVED"
            ? await this.resolveLatestApprovedReviewCommitOid(pullRequestNumber)
            : null;
        return {
            number: automationState.number,
            url: automationState.url,
            reviewDecision: automationState.reviewDecision,
            mergeability: automationState.mergeable,
            mergeStateStatus: automationState.mergeStateStatus,
            isDraft: automationState.isDraft,
            headRefOid: automationState.headRefOid,
            latestApprovedReviewCommitOid,
        };
    }
    async createPullRequest(input) {
        const response = await this.octokit.pulls.create({
            owner: this.project.owner,
            repo: this.project.repo,
            title: input.title,
            body: input.body,
            head: input.headBranch,
            base: input.baseBranch,
            draft: input.draft,
        });
        return mapPullRequest(response.data);
    }
    async listPullRequestChangedFiles(pullRequestNumber) {
        const files = (await this.octokit.paginate(this.octokit.pulls.listFiles, {
            owner: this.project.owner,
            repo: this.project.repo,
            // biome-ignore lint/style/useNamingConvention: GitHub REST parameter name.
            pull_number: pullRequestNumber,
            // biome-ignore lint/style/useNamingConvention: GitHub REST parameter name.
            per_page: constants_2.REPOSITORY_HOST_PROVIDER_PAGE_LIMIT,
        }));
        return files.flatMap((file) => file.filename
            ? [
                {
                    path: file.filename,
                    ...(file.status !== undefined ? { status: file.status } : {}),
                    ...(file.additions !== undefined
                        ? { additions: file.additions }
                        : {}),
                    ...(file.deletions !== undefined
                        ? { deletions: file.deletions }
                        : {}),
                    ...(file.changes !== undefined ? { changes: file.changes } : {}),
                },
            ]
            : []);
    }
    async listPullRequestCommits(pullRequestNumber) {
        const commits = (await this.octokit.paginate(this.octokit.pulls.listCommits, {
            owner: this.project.owner,
            repo: this.project.repo,
            // biome-ignore lint/style/useNamingConvention: GitHub REST parameter name.
            pull_number: pullRequestNumber,
            // biome-ignore lint/style/useNamingConvention: GitHub REST parameter name.
            per_page: constants_2.REPOSITORY_HOST_PROVIDER_PAGE_LIMIT,
        }));
        return commits.flatMap((commit) => {
            if (!commit.sha) {
                return [];
            }
            const authorName = commit.commit?.author?.name ?? commit.author?.login ?? null;
            return [
                {
                    oid: commit.sha,
                    title: firstLine(commit.commit?.message ?? ""),
                    ...(authorName !== null ? { authorName } : {}),
                    ...(commit.commit?.author?.date !== undefined
                        ? { authoredAt: commit.commit.author.date }
                        : {}),
                    ...(commit.html_url !== undefined ? { url: commit.html_url } : {}),
                },
            ];
        });
    }
    async mergePullRequest(pullRequestNumber, method) {
        await this.octokit.pulls.merge({
            owner: this.project.owner,
            repo: this.project.repo,
            // biome-ignore lint/style/useNamingConvention: GitHub REST parameter name.
            pull_number: pullRequestNumber,
            // biome-ignore lint/style/useNamingConvention: GitHub REST parameter name.
            merge_method: mapMergeMethod(method),
        });
    }
    async isPullRequestHeadIncludedInBase(pullRequestNumber) {
        const pullRequest = await this.getPullRequestDetails(pullRequestNumber);
        const baseBranch = pullRequest.base?.ref ?? "";
        const headSha = pullRequest.head?.sha ?? "";
        if (!baseBranch || !headSha) {
            return false;
        }
        const response = await this.octokit.request("GET /repos/{owner}/{repo}/compare/{basehead}", {
            owner: this.project.owner,
            repo: this.project.repo,
            basehead: `${baseBranch}...${headSha}`,
        });
        const comparison = response.data;
        return (comparison.ahead_by ?? comparison.total_commits ?? 0) === 0;
    }
    async syncPullRequestAssignees(pullRequestNumber, assignees) {
        const normalizedAssignees = (0, login_support_1.normalizeLogins)(assignees);
        if (normalizedAssignees.length === 0) {
            return;
        }
        const pullRequest = await this.getPullRequestDetails(pullRequestNumber);
        const assigneesToAdd = (0, login_support_1.findMissingLogins)({
            requestedLogins: normalizedAssignees,
            existingLogins: pullRequest.assignees.map((assignee) => assignee.login),
        });
        if (assigneesToAdd.length === 0) {
            return;
        }
        await this.octokit.issues.addAssignees({
            owner: this.project.owner,
            repo: this.project.repo,
            // biome-ignore lint/style/useNamingConvention: GitHub REST parameter name.
            issue_number: pullRequestNumber,
            assignees: assigneesToAdd,
        });
    }
    async syncPullRequestReviewers(pullRequestNumber, reviewers) {
        const normalizedReviewers = (0, login_support_1.normalizeLogins)(reviewers);
        if (normalizedReviewers.length === 0) {
            return { eligibleReviewers: [] };
        }
        const pullRequest = await this.getPullRequestDetails(pullRequestNumber);
        const eligibleReviewers = (0, login_support_1.findMissingLogins)({
            requestedLogins: normalizedReviewers,
            existingLogins: [],
            excludedLogins: [pullRequest.user.login],
        });
        const reviewersToRequest = (0, login_support_1.findMissingLogins)({
            requestedLogins: eligibleReviewers,
            existingLogins: pullRequest.requested_reviewers.map((reviewer) => reviewer.login),
        });
        if (reviewersToRequest.length === 0) {
            return { eligibleReviewers };
        }
        await this.octokit.pulls.requestReviewers({
            owner: this.project.owner,
            repo: this.project.repo,
            // biome-ignore lint/style/useNamingConvention: GitHub REST parameter name.
            pull_number: pullRequestNumber,
            reviewers: reviewersToRequest,
        });
        return { eligibleReviewers };
    }
    async markPullRequestReadyForReview(pullRequestNumber) {
        const pullRequest = await this.getPullRequestDetails(pullRequestNumber);
        await this.octokit.graphql(MARK_PULL_REQUEST_READY_FOR_REVIEW_MUTATION, {
            input: {
                pullRequestId: pullRequest.node_id,
            },
        });
    }
    async getPullRequestDetails(pullRequestNumber) {
        const response = await this.octokit.pulls.get({
            owner: this.project.owner,
            repo: this.project.repo,
            // biome-ignore lint/style/useNamingConvention: GitHub REST parameter name.
            pull_number: pullRequestNumber,
        });
        return response.data;
    }
    async resolveLatestApprovedReviewCommitOid(pullRequestNumber) {
        const reviews = (await this.octokit.paginate(this.octokit.pulls.listReviews, {
            owner: this.project.owner,
            repo: this.project.repo,
            // biome-ignore lint/style/useNamingConvention: GitHub REST parameter name.
            pull_number: pullRequestNumber,
            // biome-ignore lint/style/useNamingConvention: GitHub REST parameter name.
            per_page: constants_2.REPOSITORY_HOST_PROVIDER_PAGE_LIMIT,
        }));
        const latestApprovedReview = reviews
            .filter((review) => review.state === "APPROVED")
            .sort((left, right) => {
            const leftSubmittedAt = left.submitted_at ?? "";
            const rightSubmittedAt = right.submitted_at ?? "";
            return leftSubmittedAt.localeCompare(rightSubmittedAt);
        })
            .at(-1);
        return latestApprovedReview?.commit_id ?? null;
    }
}
exports.OctokitGitHubRepositoryHostClient = OctokitGitHubRepositoryHostClient;
function mapPullRequest(pullRequest) {
    return {
        number: pullRequest.number,
        url: pullRequest.html_url,
        state: pullRequest.merged_at
            ? constants_1.PLATFORM_PULL_REQUEST_STATES.merged
            : pullRequest.state === "open"
                ? constants_1.PLATFORM_PULL_REQUEST_STATES.open
                : constants_1.PLATFORM_PULL_REQUEST_STATES.closed,
        isDraft: pullRequest.draft,
        headBranch: pullRequest.head.ref,
        baseBranch: pullRequest.base.ref,
        headRefOid: pullRequest.head.sha ?? null,
        mergeCommitOid: pullRequest.merge_commit_sha ?? null,
        ...((pullRequest.merged_at ?? pullRequest.closed_at)
            ? { completedAt: pullRequest.merged_at ?? pullRequest.closed_at }
            : {}),
    };
}
function firstLine(value) {
    return value.split(/\r?\n/, 1)[0]?.trim() ?? "";
}
function mapMergeMethod(method) {
    switch (method) {
        case "squash":
            return "squash";
    }
}
const MARK_PULL_REQUEST_READY_FOR_REVIEW_MUTATION = `
  mutation MarkPullRequestReadyForReview($input: MarkPullRequestReadyForReviewInput!) {
    markPullRequestReadyForReview(input: $input) {
      pullRequest {
        id
      }
    }
  }
`;
const GET_PULL_REQUEST_AUTOMATION_STATE_QUERY = `
  query GetPullRequestAutomationState(
    $owner: String!
    $repo: String!
    $pullRequestNumber: Int!
  ) {
    repository(owner: $owner, name: $repo) {
      pullRequest(number: $pullRequestNumber) {
        number
        url
        state
        isDraft
        reviewDecision
        mergeable
        mergeStateStatus
        headRefOid
      }
    }
  }
`;
