"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.id = void 0;
exports.initPlatform = initPlatform;
exports.getRepos = getRepos;
exports.getRawFile = getRawFile;
exports.getJsonFile = getJsonFile;
exports.initRepo = initRepo;
exports.getPrList = getPrList;
exports.findPr = findPr;
exports.getPr = getPr;
exports.getBranchPr = getBranchPr;
exports.getBranchStatus = getBranchStatus;
exports.getBranchStatusCheck = getBranchStatusCheck;
exports.setBranchStatus = setBranchStatus;
exports.findIssue = findIssue;
exports.massageMarkdown = massageMarkdown;
exports.maxBodyLength = maxBodyLength;
exports.ensureIssue = ensureIssue;
exports.getIssueList = getIssueList;
exports.ensureIssueClosing = ensureIssueClosing;
exports.addAssignees = addAssignees;
exports.addReviewers = addReviewers;
exports.deleteLabel = deleteLabel;
exports.ensureComment = ensureComment;
exports.ensureCommentRemoval = ensureCommentRemoval;
exports.createPr = createPr;
exports.updatePr = updatePr;
exports.mergePr = mergePr;
const tslib_1 = require("tslib");
const node_url_1 = tslib_1.__importDefault(require("node:url"));
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const error_messages_1 = require("../../../constants/error-messages");
const logger_1 = require("../../../logger");
const common_1 = require("../../../util/common");
const git = tslib_1.__importStar(require("../../../util/git"));
const hostRules = tslib_1.__importStar(require("../../../util/host-rules"));
const bitbucket_1 = require("../../../util/http/bitbucket");
const repository_http_cache_provider_1 = require("../../../util/http/cache/repository-http-cache-provider");
const regex_1 = require("../../../util/regex");
const sanitize_1 = require("../../../util/sanitize");
const string_match_1 = require("../../../util/string-match");
const util_1 = require("../util");
const pr_body_1 = require("../utils/pr-body");
const read_only_issue_body_1 = require("../utils/read-only-issue-body");
const comments = tslib_1.__importStar(require("./comments"));
const pr_cache_1 = require("./pr-cache");
const schema_1 = require("./schema");
const utils = tslib_1.__importStar(require("./utils"));
const utils_1 = require("./utils");
exports.id = 'bitbucket';
const bitbucketHttp = new bitbucket_1.BitbucketHttp();
const BITBUCKET_PROD_ENDPOINT = 'https://api.bitbucket.org/';
let config = {};
const defaults = { endpoint: BITBUCKET_PROD_ENDPOINT };
const pathSeparator = '/';
let renovateUserUuid = null;
async function initPlatform({ endpoint, username, password, token, }) {
    if (!(username && password) && !token) {
        throw new Error('Init: You must configure either a Bitbucket token or username and password');
    }
    if (endpoint && endpoint !== BITBUCKET_PROD_ENDPOINT) {
        logger_1.logger.warn(`Init: Bitbucket Cloud endpoint should generally be ${BITBUCKET_PROD_ENDPOINT} but is being configured to a different value. Did you mean to use Bitbucket Server?`);
        defaults.endpoint = endpoint;
    }
    (0, bitbucket_1.setBaseUrl)(defaults.endpoint);
    renovateUserUuid = null;
    const options = {
        memCache: false,
    };
    if (token) {
        options.token = token;
    }
    else {
        options.username = username;
        options.password = password;
    }
    try {
        const { uuid } = (await bitbucketHttp.getJson('/2.0/user', options)).body;
        renovateUserUuid = uuid;
    }
    catch (err) {
        if (err.statusCode === 403 &&
            err.body?.error?.detail?.required?.includes('account')) {
            logger_1.logger.warn(`Bitbucket: missing 'account' scope for password`);
        }
        else {
            logger_1.logger.debug({ err }, 'Unknown error fetching Bitbucket user identity');
        }
    }
    // TODO: Add a connection check that endpoint/username/password combination are valid (#9594)
    const platformConfig = {
        endpoint: endpoint ?? BITBUCKET_PROD_ENDPOINT,
    };
    return Promise.resolve(platformConfig);
}
// Get all repositories that the user has access to
async function getRepos(config) {
    logger_1.logger.debug('Autodiscovering Bitbucket Cloud repositories');
    try {
        let { body: repos } = await bitbucketHttp.getJson(`/2.0/repositories/?role=contributor`, { paginate: true }, schema_1.Repositories);
        // if autodiscoverProjects is configured
        // filter the repos list
        const autodiscoverProjects = config.projects;
        if (is_1.default.nonEmptyArray(autodiscoverProjects)) {
            logger_1.logger.debug({ autodiscoverProjects: config.projects }, 'Applying autodiscoverProjects filter');
            repos = repos.filter((repo) => repo.projectName &&
                (0, string_match_1.matchRegexOrGlobList)(repo.projectName, autodiscoverProjects));
        }
        return repos.map(({ owner, name }) => `${owner}/${name}`);
    }
    catch (err) /* istanbul ignore next */ {
        logger_1.logger.error({ err }, `bitbucket getRepos error`);
        throw err;
    }
}
async function getRawFile(fileName, repoName, branchOrTag) {
    // See: https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Bworkspace%7D/%7Brepo_slug%7D/src/%7Bcommit%7D/%7Bpath%7D
    const repo = repoName ?? config.repository;
    const path = fileName;
    let finalBranchOrTag = branchOrTag;
    if (branchOrTag?.includes(pathSeparator)) {
        // Branch name contains slash, so we have to replace branch name with SHA1 of the head commit; otherwise the API will not work.
        finalBranchOrTag = await getBranchCommit(branchOrTag);
    }
    const url = `/2.0/repositories/${repo}/src/` +
        (finalBranchOrTag ?? `HEAD`) +
        `/${path}`;
    const res = await bitbucketHttp.get(url, {
        cacheProvider: repository_http_cache_provider_1.repoCacheProvider,
        memCache: true,
    });
    return res.body;
}
async function getJsonFile(fileName, repoName, branchOrTag) {
    // TODO #22198
    const raw = await getRawFile(fileName, repoName, branchOrTag);
    return (0, common_1.parseJson)(raw, fileName);
}
// Initialize bitbucket by getting base branch and SHA
async function initRepo({ repository, cloneSubmodules, ignorePrAuthor, bbUseDevelopmentBranch, }) {
    logger_1.logger.debug(`initRepo("${repository}")`);
    const opts = hostRules.find({
        hostType: 'bitbucket',
        url: defaults.endpoint,
    });
    config = {
        repository,
        ignorePrAuthor,
    };
    let info;
    let mainBranch;
    try {
        const { body: repoInfo } = await bitbucketHttp.getJson(`/2.0/repositories/${repository}`, schema_1.RepoInfo);
        info = repoInfo;
        mainBranch = info.mainbranch;
        if (bbUseDevelopmentBranch) {
            // Fetch Bitbucket development branch
            const developmentBranch = (await bitbucketHttp.getJson(`/2.0/repositories/${repository}/branching-model`)).body.development?.branch?.name;
            if (developmentBranch) {
                mainBranch = developmentBranch;
            }
        }
        config.defaultBranch = mainBranch;
        config = {
            ...config,
            owner: info.owner,
            mergeMethod: info.mergeMethod,
            has_issues: info.has_issues,
            is_private: info.is_private,
        };
        logger_1.logger.debug(`${repository} owner = ${config.owner}`);
    }
    catch (err) /* istanbul ignore next */ {
        if (err.statusCode === 404) {
            throw new Error(error_messages_1.REPOSITORY_NOT_FOUND);
        }
        logger_1.logger.debug({ err }, 'Unknown Bitbucket initRepo error');
        throw err;
    }
    const { hostname } = node_url_1.default.parse(defaults.endpoint);
    // Converts API hostnames to their respective HTTP git hosts:
    // `api.bitbucket.org`  to `bitbucket.org`
    // `api-staging.<host>` to `staging.<host>`
    // TODO #22198
    const hostnameWithoutApiPrefix = (0, regex_1.regEx)(/api[.|-](.+)/).exec(hostname)?.[1];
    const auth = opts.token
        ? `x-token-auth:${opts.token}`
        : `${opts.username}:${opts.password}`;
    const url = git.getUrl({
        protocol: 'https',
        auth,
        hostname: hostnameWithoutApiPrefix,
        repository,
    });
    await git.initRepo({
        ...config,
        url,
        cloneSubmodules,
    });
    const repoConfig = {
        defaultBranch: mainBranch,
        isFork: info.isFork,
        repoFingerprint: (0, util_1.repoFingerprint)(info.uuid, defaults.endpoint),
    };
    return repoConfig;
}
// istanbul ignore next
function matchesState(state, desiredState) {
    if (desiredState === 'all') {
        return true;
    }
    if (desiredState.startsWith('!')) {
        return state !== desiredState.substring(1);
    }
    return state === desiredState;
}
async function getPrList() {
    logger_1.logger.trace('getPrList()');
    return await pr_cache_1.BitbucketPrCache.getPrs(bitbucketHttp, config.repository, renovateUserUuid);
}
async function findPr({ branchName, prTitle, state = 'all', includeOtherAuthors, }) {
    logger_1.logger.debug(`findPr(${branchName}, ${prTitle}, ${state})`);
    if (includeOtherAuthors) {
        // PR might have been created by anyone, so don't use the cached Renovate PR list
        const prs = (await bitbucketHttp.getJson(`/2.0/repositories/${config.repository}/pullrequests?q=source.branch.name="${branchName}"&state=open`)).body.values;
        if (prs.length === 0) {
            logger_1.logger.debug(`No PR found for branch ${branchName}`);
            return null;
        }
        return utils.prInfo(prs[0]);
    }
    const prList = await getPrList();
    const pr = prList.find((p) => p.sourceBranch === branchName &&
        (!prTitle || p.title.toUpperCase() === prTitle.toUpperCase()) &&
        matchesState(p.state, state));
    if (!pr) {
        return null;
    }
    logger_1.logger.debug(`Found PR #${pr.number}`);
    /**
     * Bitbucket doesn't support renaming or reopening declined PRs.
     * Instead, we have to use comment-driven signals.
     */
    if (pr.state === 'closed') {
        const reopenComments = await comments.reopenComments(config, pr.number);
        if (is_1.default.nonEmptyArray(reopenComments)) {
            if (config.is_private) {
                // Only workspace members could have commented on a private repository
                logger_1.logger.debug(`Found '${comments.REOPEN_PR_COMMENT_KEYWORD}' comment from workspace member. Renovate will reopen PR ${pr.number} as a new PR`);
                return null;
            }
            for (const comment of reopenComments) {
                if (await isAccountMemberOfWorkspace(comment.user, config.repository)) {
                    logger_1.logger.debug(`Found '${comments.REOPEN_PR_COMMENT_KEYWORD}' comment from workspace member. Renovate will reopen PR ${pr.number} as a new PR`);
                    return null;
                }
            }
        }
    }
    return pr;
}
// Gets details for a PR
async function getPr(prNo) {
    const pr = (await bitbucketHttp.getJson(`/2.0/repositories/${config.repository}/pullrequests/${prNo}`)).body;
    // istanbul ignore if
    if (!pr) {
        return null;
    }
    const res = {
        ...utils.prInfo(pr),
    };
    if (is_1.default.nonEmptyArray(pr.reviewers)) {
        res.reviewers = pr.reviewers
            .map(({ uuid }) => uuid)
            .filter(is_1.default.nonEmptyString);
    }
    return res;
}
const escapeHash = (input) => input?.replace((0, regex_1.regEx)(/#/g), '%23');
// Return the commit SHA for a branch
async function getBranchCommit(branchName) {
    try {
        const branch = (await bitbucketHttp.getJson(`/2.0/repositories/${config.repository}/refs/branches/${escapeHash(branchName)}`)).body;
        return branch.target.hash;
    }
    catch (err) /* istanbul ignore next */ {
        logger_1.logger.debug({ err }, `getBranchCommit('${branchName}') failed'`);
        return undefined;
    }
}
// Returns the Pull Request for a branch. Null if not exists.
async function getBranchPr(branchName) {
    logger_1.logger.debug(`getBranchPr(${branchName})`);
    const existingPr = await findPr({
        branchName,
        state: 'open',
    });
    return existingPr ? getPr(existingPr.number) : null;
}
async function getStatus(branchName, memCache = true) {
    const sha = await getBranchCommit(branchName);
    return (await bitbucketHttp.getJson(`/2.0/repositories/${config.repository}/commit/${sha}/statuses`, {
        paginate: true,
        memCache,
    })).body.values;
}
// Returns the combined status for a branch.
async function getBranchStatus(branchName, internalChecksAsSuccess) {
    logger_1.logger.debug(`getBranchStatus(${branchName})`);
    const statuses = await getStatus(branchName);
    logger_1.logger.debug({ branch: branchName, statuses }, 'branch status check result');
    if (!statuses.length) {
        logger_1.logger.debug('empty branch status check result = returning "pending"');
        return 'yellow';
    }
    const noOfFailures = statuses.filter((status) => status.state === 'FAILED' || status.state === 'STOPPED').length;
    if (noOfFailures) {
        return 'red';
    }
    const noOfPending = statuses.filter((status) => status.state === 'INPROGRESS').length;
    if (noOfPending) {
        return 'yellow';
    }
    if (!internalChecksAsSuccess &&
        statuses.every((status) => status.state === 'SUCCESSFUL' && status.key?.startsWith('renovate/'))) {
        logger_1.logger.debug('Successful checks are all internal renovate/ checks, so returning "pending" branch status');
        return 'yellow';
    }
    return 'green';
}
const bbToRenovateStatusMapping = {
    SUCCESSFUL: 'green',
    INPROGRESS: 'yellow',
    FAILED: 'red',
};
async function getBranchStatusCheck(branchName, context) {
    const statuses = await getStatus(branchName);
    const bbState = statuses.find((status) => status.key === context)?.state;
    // TODO #22198
    return bbToRenovateStatusMapping[bbState] || null;
}
async function setBranchStatus({ branchName, context, description, state, url: targetUrl, }) {
    const sha = await getBranchCommit(branchName);
    // TargetUrl can not be empty so default to bitbucket
    const url = targetUrl ?? /* istanbul ignore next */ 'https://bitbucket.org';
    const body = {
        name: context,
        state: utils.buildStates[state],
        key: context,
        description,
        url,
    };
    await bitbucketHttp.postJson(`/2.0/repositories/${config.repository}/commit/${sha}/statuses/build`, { body });
    // update status cache
    await getStatus(branchName, false);
}
async function findOpenIssues(title) {
    try {
        const filters = [
            `title=${JSON.stringify(title)}`,
            '(state = "new" OR state = "open")',
        ];
        if (renovateUserUuid) {
            filters.push(`reporter.uuid="${renovateUserUuid}"`);
        }
        const filter = encodeURIComponent(filters.join(' AND '));
        return ((await bitbucketHttp.getJson(`/2.0/repositories/${config.repository}/issues?q=${filter}`)).body.values || /* istanbul ignore next */ []);
    }
    catch (err) /* istanbul ignore next */ {
        logger_1.logger.warn({ err }, 'Error finding issues');
        return [];
    }
}
async function findIssue(title) {
    logger_1.logger.debug(`findIssue(${title})`);
    /* istanbul ignore if */
    if (!config.has_issues) {
        logger_1.logger.debug('Issues are disabled - cannot findIssue');
        return null;
    }
    const issues = await findOpenIssues(title);
    if (!issues.length) {
        return null;
    }
    const [issue] = issues;
    return {
        number: issue.id,
        body: issue.content?.raw,
    };
}
async function closeIssue(issueNumber) {
    await bitbucketHttp.putJson(`/2.0/repositories/${config.repository}/issues/${issueNumber}`, {
        body: { state: 'closed' },
    });
}
function massageMarkdown(input) {
    // Remove any HTML we use
    return (0, pr_body_1.smartTruncate)(input, maxBodyLength())
        .replace('you tick the rebase/retry checkbox', 'by renaming this PR to start with "rebase!"')
        .replace('checking the rebase/retry box above', 'renaming the PR to start with "rebase!"')
        .replace((0, regex_1.regEx)(/<\/?summary>/g), '**')
        .replace((0, regex_1.regEx)(/<\/?(details|blockquote)>/g), '')
        .replace((0, regex_1.regEx)(`\n---\n\n.*?<!-- rebase-check -->.*?\n`), '')
        .replace((0, regex_1.regEx)(/\]\(\.\.\/pull\//g), '](../../pull-requests/')
        .replace((0, regex_1.regEx)(/<!--renovate-(?:debug|config-hash):.*?-->/g), '');
}
function maxBodyLength() {
    return 50000;
}
async function ensureIssue({ title, reuseTitle, body, }) {
    logger_1.logger.debug(`ensureIssue()`);
    /* istanbul ignore if */
    if (!config.has_issues) {
        logger_1.logger.warn('Issues are disabled - cannot ensureIssue');
        logger_1.logger.debug(`Failed to ensure Issue with title:${title}`);
        return null;
    }
    try {
        let issues = await findOpenIssues(title);
        const description = massageMarkdown((0, sanitize_1.sanitize)(body));
        if (!issues.length && reuseTitle) {
            issues = await findOpenIssues(reuseTitle);
        }
        if (issues.length) {
            // Close any duplicates
            for (const issue of issues.slice(1)) {
                await closeIssue(issue.id);
            }
            const [issue] = issues;
            if (issue.title !== title ||
                String(issue.content?.raw).trim() !== description.trim()) {
                logger_1.logger.debug('Issue updated');
                await bitbucketHttp.putJson(`/2.0/repositories/${config.repository}/issues/${issue.id}`, {
                    body: {
                        content: {
                            raw: (0, read_only_issue_body_1.readOnlyIssueBody)(description),
                            markup: 'markdown',
                        },
                    },
                });
                return 'updated';
            }
        }
        else {
            logger_1.logger.info('Issue created');
            await bitbucketHttp.postJson(`/2.0/repositories/${config.repository}/issues`, {
                body: {
                    title,
                    content: {
                        raw: (0, read_only_issue_body_1.readOnlyIssueBody)(description),
                        markup: 'markdown',
                    },
                },
            });
            return 'created';
        }
    }
    catch (err) /* istanbul ignore next */ {
        if (err.message.startsWith('Repository has no issue tracker.')) {
            logger_1.logger.debug(`Issues are disabled, so could not create issue: ${title}`);
        }
        else {
            logger_1.logger.warn({ err }, 'Could not ensure issue');
        }
    }
    return null;
}
/* istanbul ignore next */
async function getIssueList() {
    logger_1.logger.debug(`getIssueList()`);
    if (!config.has_issues) {
        logger_1.logger.debug('Issues are disabled - cannot getIssueList');
        return [];
    }
    try {
        const filters = ['(state = "new" OR state = "open")'];
        if (renovateUserUuid) {
            filters.push(`reporter.uuid="${renovateUserUuid}"`);
        }
        const filter = encodeURIComponent(filters.join(' AND '));
        const url = `/2.0/repositories/${config.repository}/issues?q=${filter}`;
        const res = await bitbucketHttp.getJson(url, {
            cacheProvider: repository_http_cache_provider_1.repoCacheProvider,
        });
        return res.body.values || [];
    }
    catch (err) {
        logger_1.logger.warn({ err }, 'Error finding issues');
        return [];
    }
}
async function ensureIssueClosing(title) {
    /* istanbul ignore if */
    if (!config.has_issues) {
        logger_1.logger.debug('Issues are disabled - cannot ensureIssueClosing');
        return;
    }
    const issues = await findOpenIssues(title);
    for (const issue of issues) {
        await closeIssue(issue.id);
    }
}
function addAssignees(_prNr, _assignees) {
    // Bitbucket supports "participants" and "reviewers" so does not seem to have the concept of "assignee"
    logger_1.logger.warn('Cannot add assignees');
    return Promise.resolve();
}
async function addReviewers(prId, reviewers) {
    logger_1.logger.debug(`Adding reviewers '${reviewers.join(', ')}' to #${prId}`);
    // TODO #22198
    const { title } = (await getPr(prId));
    const body = {
        title,
        reviewers: reviewers.map((username) => {
            const isUUID = username.startsWith('{') &&
                username.endsWith('}') &&
                string_match_1.UUIDRegex.test(username.slice(1, -1));
            const key = isUUID ? 'uuid' : 'username';
            return {
                [key]: username,
            };
        }),
    };
    await bitbucketHttp.putJson(`/2.0/repositories/${config.repository}/pullrequests/${prId}`, {
        body,
    });
}
/* istanbul ignore next */
function deleteLabel() {
    throw new Error('deleteLabel not implemented');
}
function ensureComment({ number, topic, content, }) {
    // https://developer.atlassian.com/bitbucket/api/2/reference/search?q=pullrequest+comment
    return comments.ensureComment({
        config,
        number,
        topic,
        content: (0, sanitize_1.sanitize)(content),
    });
}
function ensureCommentRemoval(deleteConfig) {
    return comments.ensureCommentRemoval(config, deleteConfig);
}
async function sanitizeReviewers(reviewers, err) {
    if (err.statusCode === 400 && err.body?.error?.fields?.reviewers) {
        const sanitizedReviewers = [];
        const MSG_AUTHOR_AND_REVIEWER = 'is the author and cannot be included as a reviewer.';
        const MSG_MALFORMED_REVIEWERS_LIST = 'Malformed reviewers list';
        const MSG_NOT_WORKSPACE_MEMBER = 'is not a member of this workspace and cannot be added to this pull request';
        for (const msg of err.body.error.fields.reviewers) {
            // Bitbucket returns a 400 if any of the PR reviewer accounts are now inactive (ie: disabled/suspended)
            if (msg === MSG_MALFORMED_REVIEWERS_LIST) {
                logger_1.logger.debug({ err }, 'PR contains reviewers that may be either inactive or no longer a member of this workspace. Will try setting only active reviewers');
                // Validate that each previous PR reviewer account is still active
                for (const reviewer of reviewers) {
                    const reviewerUser = (await bitbucketHttp.getJson(`/2.0/users/${reviewer.uuid}`, { memCache: true })).body;
                    if (reviewerUser.account_status === 'active') {
                        // There are cases where an active user may still not be a member of a workspace
                        if (await isAccountMemberOfWorkspace(reviewer, config.repository)) {
                            sanitizedReviewers.push(reviewer);
                        }
                    }
                }
                // Bitbucket returns a 400 if any of the PR reviewer accounts are no longer members of this workspace
            }
            else if (msg.endsWith(MSG_NOT_WORKSPACE_MEMBER)) {
                logger_1.logger.debug({ err }, 'PR contains reviewer accounts which are no longer member of this workspace. Will try setting only member reviewers');
                // Validate that each previous PR reviewer account is still a member of this workspace
                for (const reviewer of reviewers) {
                    if (await isAccountMemberOfWorkspace(reviewer, config.repository)) {
                        sanitizedReviewers.push(reviewer);
                    }
                }
            }
            else if (msg.endsWith(MSG_AUTHOR_AND_REVIEWER)) {
                logger_1.logger.debug({ err }, 'PR contains reviewer accounts which are also the author. Will try setting only non-author reviewers');
                const author = msg.replace(MSG_AUTHOR_AND_REVIEWER, '').trim();
                for (const reviewer of reviewers) {
                    if (reviewer.display_name !== author) {
                        sanitizedReviewers.push(reviewer);
                    }
                }
            }
            else {
                return undefined;
            }
        }
        return sanitizedReviewers;
    }
    return undefined;
}
async function isAccountMemberOfWorkspace(reviewer, repository) {
    const workspace = repository.split('/')[0];
    try {
        await bitbucketHttp.get(`/2.0/workspaces/${workspace}/members/${reviewer.uuid}`, { memCache: true });
        return true;
    }
    catch (err) {
        // HTTP 404: User cannot be found, or the user is not a member of this workspace.
        if (err.statusCode === 404) {
            logger_1.logger.debug({ err }, `User ${reviewer.display_name} is not a member of the workspace ${workspace}. Will be removed from the PR`);
            return false;
        }
        throw err;
    }
}
// Creates PR and returns PR number
async function createPr({ sourceBranch, targetBranch, prTitle: title, prBody: description, platformPrOptions, }) {
    // labels is not supported in Bitbucket: https://bitbucket.org/site/master/issues/11976/ability-to-add-labels-to-pull-requests-bb
    const base = targetBranch;
    logger_1.logger.debug({ repository: config.repository, title, base }, 'Creating PR');
    let reviewers = [];
    if (platformPrOptions?.bbUseDefaultReviewers) {
        const reviewersResponse = (await bitbucketHttp.getJson(`/2.0/repositories/${config.repository}/effective-default-reviewers`, {
            paginate: true,
        })).body;
        reviewers = reviewersResponse.values.map((reviewer) => ({
            uuid: reviewer.user.uuid,
            display_name: reviewer.user.display_name,
        }));
    }
    const body = {
        title,
        description: (0, sanitize_1.sanitize)(description),
        source: {
            branch: {
                name: sourceBranch,
            },
        },
        destination: {
            branch: {
                name: base,
            },
        },
        close_source_branch: true,
        reviewers,
    };
    try {
        const prRes = (await bitbucketHttp.postJson(`/2.0/repositories/${config.repository}/pullrequests`, {
            body,
        })).body;
        const pr = utils.prInfo(prRes);
        await pr_cache_1.BitbucketPrCache.addPr(bitbucketHttp, config.repository, renovateUserUuid, pr);
        if (platformPrOptions?.bbAutoResolvePrTasks) {
            await autoResolvePrTasks(pr);
        }
        return pr;
    }
    catch (err) /* istanbul ignore next */ {
        // Try sanitizing reviewers
        const sanitizedReviewers = await sanitizeReviewers(reviewers, err);
        if (sanitizedReviewers === undefined) {
            logger_1.logger.warn({ err }, 'Error creating pull request');
            throw err;
        }
        else {
            const prRes = (await bitbucketHttp.postJson(`/2.0/repositories/${config.repository}/pullrequests`, {
                body: {
                    ...body,
                    reviewers: sanitizedReviewers,
                },
            })).body;
            const pr = utils.prInfo(prRes);
            await pr_cache_1.BitbucketPrCache.addPr(bitbucketHttp, config.repository, renovateUserUuid, pr);
            if (platformPrOptions?.bbAutoResolvePrTasks) {
                await autoResolvePrTasks(pr);
            }
            return pr;
        }
    }
}
async function autoResolvePrTasks(pr) {
    logger_1.logger.debug(`Auto resolve PR tasks in #${pr.number}`);
    try {
        const unResolvedTasks = (await bitbucketHttp.getJson(`/2.0/repositories/${config.repository}/pullrequests/${pr.number}/tasks`, { paginate: true, pagelen: 100 }, schema_1.UnresolvedPrTasks)).body;
        logger_1.logger.trace({
            prId: pr.number,
            listTaskRes: unResolvedTasks,
        }, 'List PR tasks');
        for (const task of unResolvedTasks) {
            const res = await bitbucketHttp.putJson(`/2.0/repositories/${config.repository}/pullrequests/${pr.number}/tasks/${task.id}`, {
                body: {
                    state: 'RESOLVED',
                    content: {
                        raw: task.content.raw,
                    },
                },
            });
            logger_1.logger.trace({
                prId: pr.number,
                updateTaskResponse: res,
            }, 'Put PR tasks - mark resolved');
        }
    }
    catch (err) {
        logger_1.logger.warn({ prId: pr.number, err }, 'Error resolving PR tasks');
    }
}
async function updatePr({ number: prNo, prTitle: title, prBody: description, state, targetBranch, }) {
    logger_1.logger.debug(`updatePr(${prNo}, ${title}, body)`);
    // Updating a PR in Bitbucket will clear the reviewers if reviewers is not present
    const pr = (await bitbucketHttp.getJson(`/2.0/repositories/${config.repository}/pullrequests/${prNo}`)).body;
    try {
        const body = {
            title,
            description: (0, sanitize_1.sanitize)(description),
            reviewers: pr.reviewers,
        };
        if (targetBranch) {
            body.destination = {
                branch: {
                    name: targetBranch,
                },
            };
        }
        await bitbucketHttp.putJson(`/2.0/repositories/${config.repository}/pullrequests/${prNo}`, { body });
    }
    catch (err) {
        // Try sanitizing reviewers
        const sanitizedReviewers = await sanitizeReviewers(pr.reviewers, err);
        if (sanitizedReviewers === undefined) {
            throw err;
        }
        else {
            await bitbucketHttp.putJson(`/2.0/repositories/${config.repository}/pullrequests/${prNo}`, {
                body: {
                    title,
                    description: (0, sanitize_1.sanitize)(description),
                    reviewers: sanitizedReviewers,
                },
            });
        }
    }
    if (state === 'closed' && pr) {
        await bitbucketHttp.postJson(`/2.0/repositories/${config.repository}/pullrequests/${prNo}/decline`);
    }
}
async function mergePr({ branchName, id: prNo, strategy: mergeStrategy, }) {
    logger_1.logger.debug(`mergePr(${prNo}, ${branchName}, ${mergeStrategy})`);
    // Bitbucket Cloud does not support a rebase-alike; https://jira.atlassian.com/browse/BCLOUD-16610
    if (mergeStrategy === 'rebase') {
        logger_1.logger.warn('Bitbucket Cloud does not support a "rebase" strategy.');
        return false;
    }
    try {
        await bitbucketHttp.postJson(`/2.0/repositories/${config.repository}/pullrequests/${prNo}/merge`, {
            body: (0, utils_1.mergeBodyTransformer)(mergeStrategy),
        });
        logger_1.logger.debug('Automerging succeeded');
    }
    catch (err) /* istanbul ignore next */ {
        logger_1.logger.debug({ err }, `PR merge error`);
        logger_1.logger.info({ pr: prNo }, 'PR automerge failed');
        return false;
    }
    return true;
}
//# sourceMappingURL=index.js.map