package com.atlassian.bitbucket.pull;

import com.atlassian.bitbucket.AuthorisationException;
import com.atlassian.bitbucket.NoSuchEntityException;
import com.atlassian.bitbucket.comment.*;
import com.atlassian.bitbucket.comment.pull.AddPullRequestFileCommentRequest;
import com.atlassian.bitbucket.comment.pull.AddPullRequestLineCommentRequest;
import com.atlassian.bitbucket.commit.Commit;
import com.atlassian.bitbucket.commit.CommitCallback;
import com.atlassian.bitbucket.commit.NoSuchCommitException;
import com.atlassian.bitbucket.content.*;
import com.atlassian.bitbucket.event.pull.*;
import com.atlassian.bitbucket.permission.Permission;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.scm.FeatureUnsupportedScmException;
import com.atlassian.bitbucket.scm.ScmFeature;
import com.atlassian.bitbucket.scm.ScmService;
import com.atlassian.bitbucket.task.Task;
import com.atlassian.bitbucket.task.TaskCount;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageRequest;
import com.atlassian.bitbucket.validation.ArgumentValidationException;
import com.atlassian.bitbucket.watcher.Watcher;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Set;

/**
 * A service for the management of pull requests and their comments.
 * <p>
 * About pull request IDs: Each pull request has two repositories associated with it: The repository commits will be
 * {@link PullRequest#getFromRef() merged from}, and the repository they will be {@link PullRequest#getToRef() merged
 * to}. Pull request {@link PullRequest#getId() ID}s are scoped to the {@link PullRequest#getToRef() target} repository,
 * and each method accepting a repository ID, unless otherwise documented, expects the ID of that repository. Also note
 * that pull request IDs start from 1 <i>in each target repository</i>; they are <i>not</i> globally unique.
 */
@SuppressWarnings("deprecation")
public interface PullRequestService extends PullRequestSupplier {

    /**
     * Adds a general comment to the pull request.
     *
     * @param repositoryId  the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @param commentText   the comment text
     * @return the created comment
     * @throws NoSuchPullRequestException if no pull request with the specified ID can be found in the repository
     */
    @Nonnull
    Comment addComment(int repositoryId, long pullRequestId, @Nonnull String commentText);

    /**
     * Adds a line comment to the pull request.
     *
     * @param request describes the comment to create
     * @return the created comment
     */
    @Nonnull
    Comment addLineComment(@Nonnull AddPullRequestLineCommentRequest request);

    /**
     * Adds a line comment to a diff in a pull request.
     *
     * @param repositoryId  the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @param request       describes the comment to create
     * @return the created line comment
     * @throws NoSuchPullRequestException if no pull request with the specified ID can be found in the repository
     * @throws ArgumentValidationException if the {@code request} contains invalid data
     * @deprecated in 4.7 for removal in 5.0. Use {@link #addLineComment(AddPullRequestLineCommentRequest)} instead.
     */
    @Nonnull
    @Deprecated
    Comment addDiffComment(int repositoryId, long pullRequestId, @Nonnull AddDiffCommentRequest request);

    /**
     * Adds a file-level comment to the pull request.
     *
     * @param request describes the comment to create
     * @return the created comment
     */
    @Nonnull
    Comment addFileComment(@Nonnull AddPullRequestFileCommentRequest request);

    /**
     * Adds a file-level comment to the pull request.
     *
     * @param repositoryId  the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @param request       describes the comment to create
     * @return the created comment
     * @throws NoSuchPullRequestException if no pull request with the specified ID can be found in the repository
     * @deprecated in 4.7 for removal in 5.0. Use {@link #addFileComment(AddPullRequestFileCommentRequest)} instead.
     */
    @Nonnull
    @Deprecated
    Comment addFileComment(int repositoryId, long pullRequestId, @Nonnull AddFileCommentRequest request);

    /**
     * Adds a reply to a comment
     *
     * @param repositoryId  the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @param commentId     the ID of the comment being replied to
     * @param commentText   the comment text
     * @return the created comment
     * @throws NoSuchPullRequestException if no pull request with the specified ID can be found in the repository
     * @throws NoSuchCommentException if no comment exists with the specified ID
     */
    @Nonnull
    Comment addReply(int repositoryId, long pullRequestId, long commentId, @Nonnull String commentText);

    /**
     * Adds the user specified by 'username' as {@link PullRequestRole#REVIEWER REVIEWER} to the provided PR.
     * If the user is already a reviewer this method has no effect.
     *
     * @param repositoryId  the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @param username      the username for the user to be added as a reviewer
     * @return the newly created or updated participant entity
     * @throws NoSuchPullRequestException if no pull request with the specified ID can be found in the repository
     * @throws UnmodifiablePullRequestRoleException if the participant is already assigned the
     *         {@link PullRequestRole#AUTHOR AUTHOR} role.
     * @throws InvalidPullRequestParticipantException if the user cannot be made a reviewer because they have
     *         insufficient access to the repository
     * @throws IllegalPullRequestStateException if the pull request is either {@link PullRequestState#MERGED MERGED}
     *         or {@link PullRequestState#DECLINED DECLINED}
     * @since 4.2
     */
    @Nonnull
    PullRequestParticipant addReviewer(int repositoryId, long pullRequestId, @Nonnull String username);

    /**
     * Allows a user to give their approval to a pull request. If they aren't already a participant of the pull request
     * they are made one first. If the user has already already provided their approval this operation has no effect. A
     * user may only approve a pull request which is in open.
     * <p>
     * Since 4.2 authors may not have their status changed.
     *
     * @param repositoryId  the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @return the newly created or updated participant entity
     * @throws InvalidPullRequestParticipantException if the user cannot be made a participant because they have
     *         insufficient privileges to read the repository
     * @throws IllegalPullRequestStateException if the pull request is not open
     * @deprecated in 4.2, to be removed in 5.0. Use {@link #setReviewerStatus(int, long,
     * PullRequestParticipantStatus)} instead.
     */
    @Nonnull
    @Deprecated
    PullRequestParticipant approve(int repositoryId, long pullRequestId);

    /**
     * Assigns a participant to an explicit role in pull request. Currently only {@link PullRequestRole#REVIEWER
     * reviewers} may be assigned.
     *
     * If the user is not yet a participant in the pull request, they are made one and assigned the supplied role.
     * If the user is already a participant in the pull request, their previous role is replaced with the supplied role
     * unless they are already assigned the {@link PullRequestRole#AUTHOR AUTHOR} role which cannot be changed
     * and will result in an {@link UnmodifiablePullRequestRoleException} being thrown.
     *
     * @param repositoryId  the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @param username      the participant's user name
     * @param role          the participant's role in the pull request
     * @return the newly created or updated participant
     * @throws InvalidPullRequestRoleException if the role is not the {@link PullRequestRole#REVIEWER REVIEWER} role.
     * @throws InvalidPullRequestParticipantException if the user cannot be made a participant because they have
     *         insufficient privileges to read the repository
     * @throws UnmodifiablePullRequestRoleException if the participant is already assigned the
     *         {@link PullRequestRole#AUTHOR AUTHOR} role.
     * @throws NoSuchPullRequestException if no pull request with the specified ID can be found in the repository
     *
     * @deprecated in 4.2 for removal in 5.0. Use {@link #addReviewer(int, long, String)} instead.
     */
    @Nonnull
    @Deprecated
    PullRequestParticipant assignRole(int repositoryId, long pullRequestId, @Nonnull String username,
                                      @Nonnull PullRequestRole role);

    /**
     * Retrieves a flag indicating whether the specified pull request can be {@link #merge(PullRequestMergeRequest) merged}
     * by the system or must be manually merged by a user.
     * <p>
     * Even if this method returns {@code true}, there are still a variety of reasons for which merging may fail,
     * including but not limited to:
     * <ul>
     *     <li>The version supplied when merging is out of date</li>
     *     <li>Additional changes have been pushed to the pull request's target branch and the pull request has not
     *     yet been updated accordingly</li>
     * </ul>
     * The goal for this method is to serve as a hint as to whether or not a given pull request can be merged by the
     * system, rather than as an ironclad assurance that it can be.
     * <p>
     * Note: If the specified pull request is {@link PullRequestState#DECLINED declined} or
     * {@link PullRequestState#MERGED}, this method will always return {@code false}.
     *
     * @param repositoryId  the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @return the mergeability of the pull request, taking into account if it is open, has conflicts and any
     *         vetoes that {@code MergeRequestCheck}s may have raised
     * @throws IllegalPullRequestStateException if the pull request is not open
     */
    PullRequestMergeability canMerge(int repositoryId, long pullRequestId);

    /**
     * Counts the number of commits a pull request has.
     *
     * @param repositoryId  the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @return the number of commits in the pull request
     * @throws NoSuchPullRequestException if no pull request with the specified ID can be found in the repository
     */
    long countCommits(int repositoryId, long pullRequestId);

    /**
     * Counts the number of pull requests which match the specified search criteria.
     *
     * @param request     the search criteria to use
     * @return the number of pull requests that match the search criteria
     */
    long count(@Nonnull PullRequestSearchRequest request);

    /**
     * Counts the number of tasks in a pull request.
     *
     * @param searchRequest  the search criteria to use
     * @return a {@link TaskCount} object containing the number of tasks in each state
     * @throws NoSuchPullRequestException if no pull request with the specified ID can be found in the repository
     */
    @Nonnull
    TaskCount countTasks(@Nonnull PullRequestTaskSearchRequest searchRequest);

    /**
     * Creates a pull request from the supplied repository and branch to the supplied repository and branch with the
     * given title and description.
     * <p>
     * Events raised:
     * <ul>
     *     <li>{@link PullRequestOpenRequestedEvent PullRequestOpenRequestedEvent}</li>
     *     <li>{@link PullRequestOpenedEvent PullRequestOpenedEvent}</li>
     * </ul>
     *
     * @param title          the title of the pull request
     * @param description    the optional description of the pull request
     * @param reviewers      the set of usernames of the users to add as a reviewer
     * @param fromRepository the repository from which the changes come
     * @param fromBranchId   the branch of the fromRepository from which the changes come
     * @param toRepository   the repository changes are to be merged to
     * @param toBranchId     the branch of the toRepository to which changes are merged
     * @return               the created pull request
     * @throws DuplicatePullRequestException if there is already an open pull request with identical to and from
     *         repositories and branches
     * @throws EmptyPullRequestException if the to branch is up-to-date with all of the changes on the from branch,
     *         resulting in a pull request with nothing to merge
     * @throws InvalidPullRequestTargetException if the from or to repositories <i>are not</i> the same or if the from
     *         and to branches <i>are</i> the same
     * @throws InvalidPullRequestReviewersException if one or more of the reviewers could not be added
     * @throws PullRequestOpenCanceledException if opening the pull request is canceled by an event listener
     * @throws NoSuchCommitException if the the from or to branches do not exist
     */
    @Nonnull
    PullRequest create(@Nonnull String title, @Nullable String description, @Nonnull Set<String> reviewers,
                       @Nonnull Repository fromRepository, @Nonnull String fromBranchId,
                       @Nonnull Repository toRepository, @Nonnull String toBranchId);

    /**
     * Declines a pull request. The pull request must be open or an exception is thrown. The expected version of
     * the pull request must be supplied to ensure the client is not operating on stale information.
     *
     * @param request details for declining a pull request
     * @return the updated pull request
     * @throws IllegalPullRequestStateException if the pull request is not open
     * @throws NoSuchPullRequestException if no pull request with the specified ID can be found in the repository
     * @throws PullRequestOutOfDateException if the version of the specified pull request does not match the expected version
     */
    @Nonnull
    PullRequest decline(@Nonnull PullRequestDeclineRequest request);

    /**
     * Deletes a pull request comment
     *
     * @param repositoryId   the ID of the repository to which the pull request will be merged
     * @param pullRequestId  the ID of the pull request within the repository
     * @param commentId      the ID of the comment
     * @param commentVersion the expected version of the comment
     * @return {@code true} if the comment existed (and thus was deleted), {@code false} otherwise
     * @throws CommentDeletionException if the comment has replies
     * @throws CommentOutOfDateException if the version of the specified comment does not match the expected version
     */
    boolean deleteComment(int repositoryId, long pullRequestId, long commentId, int commentVersion);

    /**
     * Get diff anchors for a given pull request and a path affected by that pull request.
     *
     * @param repositoryId  the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @param path          the path of the file modified by the pull request, for which the anchors should be retrieved
     * @return all anchors (representing comments) for the {@code path} in the pull request identified by
     *         {@code repositoryId} and {@code pullRequestId}
     * @throws NoSuchPullRequestException if no pull request with the specified ID can be found in the repository
     */
    @Nonnull
    Iterable<DiffCommentAnchor> findCommentAnchors(int repositoryId, long pullRequestId, @Nonnull String path);

    /**
     * Retrieves a page of the activities for a given pull request.
     *
     * @param repositoryId  the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @param pageRequest   the request specifying the start and limit of the page
     * @return the page of activities
     */
    @Nonnull
    Page<PullRequestActivity> getActivities(int repositoryId, long pullRequestId,
                                            @Nonnull PageRequest pageRequest);

    /**
     * Retrieves the page of activities that start with an entity (given its type and id) for the given pull request.
     * <p>
     * Entity types currently accepted:
     * <ul>
     *  <li>{@link PullRequestEntityType#ACTIVITY} to search for the page of activities starting with
     *      the supplied activity
     *  </li>
     *  <li>
     *      {@link PullRequestEntityType#COMMENT} to search for the page of activities starting with
     *      the activity of the supplied comment. If the comment is not a root comment, the comment's
     *      root's activity is used.
     *  </li>
     * </ul>
     *
     * @param repositoryId the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @param fromType the type of the entity to search for
     * @param fromId ID of the entity to search for - either a comment ID or activity ID
     * @param pageRequest the request specifying the limit of the page. Note: the start property is ignored.
     * @return the page of activities that contains the given activity. This page provides the ability to
     *         calculate both the next page and the previous page of results as the page may be
     * @throws NoSuchEntityException if the activity can't be found.
     */
    @Nonnull
    PullRequestActivityPage<PullRequestActivity> getActivitiesStartingAt(int repositoryId, long pullRequestId,
                                                                         @Nonnull PullRequestEntityType fromType,
                                                                         long fromId, @Nonnull PageRequest pageRequest);

    /**
     * Retrieves the specified activities of the given pull request.
     *
     * @param repositoryId the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @param activityIds the activity IDs that should be retrieved
     * @return the activities that could be found. Note: There may be fewer returned activities than supplied IDs where
     *         an unknown ID is specified or where activities have since been deleted.
     */
    @Nonnull
    Set<PullRequestActivity> getActivitiesById(int repositoryId, long pullRequestId, Set<Long> activityIds);

    /**
     * Retrieves a pull request by its ID, within the specified repository.
     *
     * @param repositoryId  the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @return the pull request found or null if there is no pull request that matches the given id
     */
    @Nullable
    @Override
    PullRequest getById(int repositoryId, long pullRequestId);

    /**
     * Retrieves a comment for the given comment id and pull request id.
     *
     * @param repositoryId  the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @param commentId     the ID of the comment to retrieve
     * @return the comment
     * @throws NoSuchCommentException if the comment can not be found
     * @throws NoSuchPullRequestException if the pull request can not be found
     */
    @Nonnull
    Comment getComment(int repositoryId, long pullRequestId, long commentId);

    /**
     * Retrieves a page of the commits for a given pull request.
     *
     * @param repositoryId  the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @param pageRequest   the request specifying the start and limit of the page
     * @return the page of commits
     * @throws NoSuchPullRequestException if no pull request with the specified ID can be found in the repository
     */
    @Nonnull
    Page<Commit> getCommits(int repositoryId, long pullRequestId, @Nonnull PageRequest pageRequest);

    /**
     * Retrieves the {@link PullRequestMergeConfig merge configuration} for the specified source. All of
     * the <i>supported</i> {@link PullRequestMergeStrategy merge strategies} are returned, but only
     * {@link PullRequestMergeStrategy#isEnabled() enabled} strategies can be specified when {@link #merge
     * merging} pull requests. At least one strategy, the {@link PullRequestMergeConfig#getDefaultStrategy
     * default}, is <i>guaranteed</i> to be enabled.
     * <p>
     * For {@link PullRequest#isCrossRepository cross-repository} pull requests, the configuration for the
     * {@link PullRequest#getToRef target repository} controls how the pull request can be merged.
     * <p>
     * Merge configuration can be applied at 3 different levels, listed from most specific to most generic:
     * <ol>
     *     <li>{@link PullRequestMergeConfigType#REPOSITORY Repository}</li>
     *     <li>{@link PullRequestMergeConfigType#PROJECT Project} and SCM
     *     <ul>
     *         <li>The SCM must also be considered because projects can contain multiple repositories which
     *         may not always use the same SCM</li>
     *     </ul>
     *     </li>
     *     <li>{@link PullRequestMergeConfigType#SCM}</li>
     * </ol>
     * If merge configuration has not been applied at any level, {@link PullRequestMergeConfigType#DEFAULT
     * defaults} for the appropriate SCM are returned.
     * <p>
     * SCMs are <i>not required</i> to support merge strategies. Plugins should {@link ScmService#isSupported
     * check support} for the {@link ScmFeature#MERGE_STRATEGIES MERGE_STRATEGIES} feature prior to calling
     * this method. If the SCM does not support merge strategies, an exception will be thrown.
     *
     * @param request a request describing the repository, project-and-SCM or SCM to retrieve configuration for
     * @return merge configuration for pull requests {@link PullRequest#getToRef to} the specified repository,
     *         repositories within the specified project, or repositories of the specified SCM
     * @throws AuthorisationException if the requesting user does not have access to the source they're retrieving
     *                                merge configuration for
     * @throws FeatureUnsupportedScmException if the SCM does not support merge strategies
     * @since 4.9
     */
    @Nonnull
    PullRequestMergeConfig getMergeConfig(@Nonnull GetPullRequestMergeConfigRequest request);

    /**
     * Retrieves a page of the participants for a given pull request.
     *
     * @param repositoryId  the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @param pageRequest   the request specifying the start and limit of the page
     * @return the page of participants
     */
    @Nonnull
    Page<PullRequestParticipant> getParticipants(int repositoryId, long pullRequestId,
                                                 @Nonnull PageRequest pageRequest);

    /**
     * Merges a pull request. The pull request must be open or an exception is thrown. The expected version of
     * the pull request must be supplied to ensure the client is not operating on stale information.
     *
     * @param request details for merging the pull request, specifying the pull request to merge, the expected
     *                version, and, optionally, a commit message and additional context
     * @return the updated pull request
     * @throws IllegalPullRequestStateException if the pull request is not open
     * @throws NoSuchPullRequestException if no pull request with the specified ID can be found in the repository
     * @throws PullRequestMergeCanceledException if the merge was canceled by an event listener
     * @throws PullRequestMergeVetoedException if any {@code MergeRequestCheck}s vetoed the merge
     * @throws PullRequestOutOfDateException if the version of the specified pull request does not match the expected version
     */
    @Nonnull
    PullRequest merge(@Nonnull PullRequestMergeRequest request);

    /**
     * Removes a {@link PullRequestRole#REVIEWER REVIEWER} from a pull request.
     * <p>
     * If the user is the {@link PullRequestRole#AUTHOR AUTHOR} of the pull request this method will throw
     * {@link IllegalPullRequestStateException}; the author may not be removed.
     * <p>
     * If the user has no {@link PullRequestActivity activity} items associated with them in the
     * pull request they will be completely removed. Otherwise they will be made a {@link PullRequestRole#PARTICIPANT
     * PARTICIPANT} instead.
     *
     * @param repositoryId  the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @param username      the reviewer's user name
     * @throws NoSuchPullRequestException if no pull request with the specified ID can be found in the repository
     * @throws UnmodifiablePullRequestRoleException if the user is the {@link PullRequestRole#AUTHOR AUTHOR} of the PR
     * @throws IllegalPullRequestStateException if the pull request is either {@link PullRequestState#MERGED MERGED}
     *                                          or {@link PullRequestState#DECLINED DECLINED}
     * @since 4.2
     */
    void removeReviewer(int repositoryId, long pullRequestId, @Nonnull String username);

    /**
     * Reopens a pull request. The pull request must be declined or an exception is thrown. The expected version of
     * the pull request must be supplied to ensure the client is not operating on stale information.
     *
     * @param repositoryId  the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @param version       the expected version of the pull request
     * @return the updated pull request
     * @throws DuplicatePullRequestException if a pull request with the same source and target refs already exists
     * @throws IllegalPullRequestStateException if the pull request is not declined
     * @throws NoSuchPullRequestException if no pull request with the specified ID can be found in the repository
     * @throws PullRequestOutOfDateException if the version of the specified pull request does not match the expected version
     */
    @Nonnull
    PullRequest reopen(int repositoryId, long pullRequestId, int version);

    /**
     * Finds a single page of pull requests depending on the {@link PullRequestSearchRequest request} passed in.
     * If the request specifies that the results should be sorted by {@link PullRequestOrder#PARTICIPANT_STATUS participant status}
     * then only one participant may be specified in the request
     *
     * @param request     the search criteria to use
     * @param pageRequest the request specifying the start and limit of the search
     * @return a page of pull requests
     * @throws IllegalPullRequestSearchRequestException if {@link PullRequestOrder#PARTICIPANT_STATUS PARTICIPANT_STATUS} order
     * was requested but {@link PullRequestSearchRequest#getParticipants()} is empty or contains more than one participant (since 4.4)
     */
    @Nonnull
    Page<PullRequest> search(@Nonnull PullRequestSearchRequest request, @Nonnull PageRequest pageRequest);

    /**
     * Finds a single page of pull request activities depending on the {@link PullRequestActivitySearchRequest request}
     * passed in.
     *
     * @param request     the search criteria to use
     * @param pageRequest the request specifying the start and limit of the search
     * @return a page of pull request activities
     */
    @Nonnull
    Page<PullRequestActivity> searchActivities(@Nonnull PullRequestActivitySearchRequest request,
                                               @Nonnull PageRequest pageRequest);

    /**
     * Finds a single page of @{link Task tasks} belonging to this pull request depending on the
     * {@link PullRequestTaskSearchRequest request} passed in.
     *
     * @param searchRequest the search criteria to use
     * @param pageRequest   the request specifying the start and limit of the search
     * @return a page of pull request activities
     */
    @Nonnull
    Page<Task> searchTasks(@Nonnull PullRequestTaskSearchRequest searchRequest, @Nonnull PageRequest pageRequest);

    /**
     * Finds {@link ApplicationUser users} who have participated in pull requests based on the provided
     * {@link PullRequestParticipantSearchRequest search request}.
     *
     * @param searchRequest the search criteria to use
     * @param pageRequest   the request specifying the start and limit of the search
     * @return a page of pull request participant users
     * @since 4.2
     */
    @Nonnull
    Page<ApplicationUser> searchUsers(@Nonnull PullRequestParticipantSearchRequest searchRequest,
                                      @Nonnull PageRequest pageRequest);

    /**
     * Sets the {@link PullRequestMergeConfig merge configuration} for the specified source, setting the
     * default strategy and the complete set of enabled strategies. Any enabled strategy may be requested
     * when merging a pull request, and the specified default <i>must be enabled</i>. The enabled strategies
     * are verified against the SCM to ensure they are all supported.
     * <p>
     * For {@link PullRequest#isCrossRepository cross-repository} pull requests, the configuration for the
     * {@link PullRequest#getToRef target repository} controls how the pull request can be merged.
     * <p>
     * Merge configuration can be applied at 3 different levels, listed from most specific to most generic,
     * and can only be updated by administrators:
     * <ol>
     *     <li>{@link PullRequestMergeConfigType#REPOSITORY Repository}
     *     <ul>
     *         <li>Requires {@link Permission#REPO_ADMIN REPO_ADMIN} permission</li>
     *     </ul>
     *     </li>
     *     <li>{@link PullRequestMergeConfigType#PROJECT Project} and SCM
     *     <ul>
     *         <li>The SCM must also be considered because projects can contain multiple repositories which
     *         may not always use the same SCM</li>
     *         <li>Requires {@link Permission#PROJECT_ADMIN PROJECT_ADMIN} permission</li>
     *     </ul>
     *     </li>
     *     <li>{@link PullRequestMergeConfigType#SCM}
     *     <ul>
     *         <li>Requires {@link Permission#ADMIN global ADMIN} permission</li>
     *     </ul>
     *     </li>
     * </ol>
     * <p>
     * SCMs are <i>not required</i> to support merge strategies. Plugins should {@link ScmService#isSupported
     * check support} for the {@link ScmFeature#MERGE_STRATEGIES MERGE_STRATEGIES} feature prior to calling
     * this method. If the SCM does not support merge strategies, an exception will be thrown.
     *
     * @param request a request describing the repository, project-and-SCM or SCM to set configuration for, the
     *                default strategy to use and the set of strategies that should be enabled
     * @throws ArgumentValidationException if the default merge strategy is not enabled, or if any of the enabled
     *                                     strategies are not supported by the associated SCM
     * @throws AuthorisationException if the updating user does not have the required administrator permission
     * @throws FeatureUnsupportedScmException if the SCM does not support merge strategies
     * @since 4.9
     */
    void setMergeConfig(@Nonnull SetPullRequestMergeConfigRequest request);

    /**
     * Allows a user to update their participation status in a pull request.
     * If they aren't already a participant of the pull request they are made one first. A non-reviewer may not
     * set their status to {@link PullRequestParticipantStatus#UNAPPROVED UNAPPROVED}.
     * A user may only update their status if a pull request is {@link PullRequestState#OPEN OPEN}.
     * This operation has no effect if the user already has their status set to the provided value.
     * <p>
     * Authors may not have their status changed.
     *
     * @param repositoryId  the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @param status the participant status to set for the current user
     * @return the newly created or updated participant entity
     * @throws InvalidPullRequestParticipantException if the user cannot be made a participant because they have
     *         insufficient privileges to read the repository
     * @throws IllegalPullRequestStateException if the pull request is not open
     * @since 4.2
     */
    @Nonnull
    PullRequestParticipant setReviewerStatus(int repositoryId, long pullRequestId,
                                             @Nonnull PullRequestParticipantStatus status);

    /**
     * Streams {@link Change changes} for the specified {@link PullRequest} to the provided {@link ChangeCallback}.
     * <p>
     * Note: Internal hard limits are applied to the number of {@link Change changes} which will be returned. When
     * changes are truncated for exceeding those limits, it will be signalled to the callback.
     *
     * @param request  the repository, pull request and other properties describing the changes to stream
     * @param callback the callback to receive {@link Change changes}
     * @throws NoSuchPullRequestException if no pull request with the specified ID can be found in the repository
     */
    void streamChanges(@Nonnull PullRequestChangesRequest request, @Nonnull ChangeCallback callback);

    /**
     * Streams commits for the specified pull request.
     *
     * @param repositoryId  the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @param callback      the request specifying the start and limit of the page
     * @throws NoSuchPullRequestException if no pull request with the specified ID can be found in the repository
     */
    void streamCommits(int repositoryId, long pullRequestId, @Nonnull CommitCallback callback);

    /**
     * Streams the {@link Diff} for the specified file {@link PullRequestDiffRequest#getPath()
     * path} within the {@link PullRequest} to the provided
     * {@link DiffContentCallback}. Comment anchors will be {@link DiffContentCallback#offerAnchors(java.util.List)
     * offered} to the callback <i>prior</i> to streaming the diff, allowing them to be merged into the diff output
     * as desired.
     * <p>
     * If the {@link Change} indicated the {@link PullRequestDiffRequest#getPath()
     * path} being diffed was {@link ChangeType#MOVE moved/renamed} or
     * {@link ChangeType#COPY copied}, the {@link Change#getSrcPath() srcPath} should also be provided. Failure to
     * provide both paths may result in an incorrect diff in some SCMs.
     * <p>
     * Note: Internal hard limits are applied to the diff being streamed, limiting the number of lines that can be
     * returned. When a diff is truncated for exceeding those limits, it will be signalled to the callback.
     *
     * @param request  the repository, pull request, path and other properties describing the diff to stream
     * @param callback the callback to receive the streamed {@link Diff}
     * @throws NoSuchPullRequestException if no pull request with the specified ID can be found in the repository
     */
    void streamDiff(@Nonnull PullRequestDiffRequest request, @Nonnull DiffContentCallback callback);

    /**
     * Unassigns a participant from any role they may have been given in a pull request.
     *
     * If the user is not a participant this method has no effect.
     *
     * An attempt to unassign a participant from the {@link PullRequestRole#AUTHOR AUTHOR} role
     * will result in an {@link UnmodifiablePullRequestRoleException} being thrown.
     *
     * Since 4.2 if the user is not an {@link PullRequestRole#AUTHOR AUTHOR} and they have
     * no {@link PullRequestActivity activity} items associated with them in the pull request they will be
     * completely removed.
     *
     * If the user has the {@link PullRequestRole#PARTICIPANT PARTICIPANT} role and they have
     * {@link PullRequestActivity activity} items associated with them in the pull request this method has no effect.
     *
     * If the user has the {@link PullRequestRole#REVIEWER REVIEWER} role and they have
     * {@link PullRequestActivity activity} items associated with them in the pull request this method
     * will simply update their role to {@link PullRequestRole#PARTICIPANT PARTICIPANT}.
     *
     * @param repositoryId  the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @param username      the participant's user name
     * @throws NoSuchPullRequestException if no pull request with the specified ID can be found in the repository
     * @throws UnmodifiablePullRequestRoleException if the participant is already an {@link PullRequestRole#AUTHOR AUTHOR}
     *
     * @deprecated in 4.2 for removal in 5.0. Use {@link #removeReviewer} instead
     */
    @Deprecated
    void unassignRole(int repositoryId, long pullRequestId, @Nonnull String username);

    /**
     * Removes the current user as a watcher of the given pull request.
     *
     * @param repositoryId  the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @return true if the watcher existed (and thus was removed), false otherwise
     */
    boolean unwatch(int repositoryId, long pullRequestId);

    /**
     * Updates a pull request's title and description.
     * <p>
     * This method will fire:
     * <ul>
     *     <li>{@link PullRequestUpdatedEvent PullRequestUpdatedEvent}
     *     to inform listeners that the title or description have changed</li>
     *     <li>{@link PullRequestReviewersUpdatedEvent PullRequestReviewersUpdatedEvent}
     *     to inform listeners of any reviewers assigned or unassigned from this role</li>
     *     <li>{@link PullRequestParticipantsUpdatedEvent PullRequestParticipantsAddedEvent}
     *     to inform listeners of any users who have participated in the pull request for the first time</li>
     *     <li>{@link PullRequestRescopedEvent PullRequestRescopedEvent}
     *     to inform listeners the the destination branch has been updated and rescope has occurred</li>
     * </ul>
     *
     * @param request details for updating a pull request, including a title, description and reviewers.
     * @return the updated pull request
     * @throws NoSuchPullRequestException if no pull request with the specified ID can be found in the repository
     * @throws PullRequestOutOfDateException if the version of the specified pull request does not match the expected version
     * @throws InvalidPullRequestReviewersException if one or more of the reviewers could not be added
     * @throws DuplicatePullRequestException if there is already an open pull request with an identical to branch
     * @throws InvalidPullRequestTargetException if the from and new to branch are the same
     * @throws EmptyPullRequestException if the new destination branch up-to-date is up-to-date with all of the changes
     *         from the from branch, resulting in a pull request with nothing to merge
     */
    @Nonnull
    PullRequest update(@Nonnull PullRequestUpdateRequest request);

    /**
     * Updates the pull request comment's text
     *
     * @param repositoryId   the ID of the repository to which the pull request will be merged
     * @param pullRequestId  the ID of the pull request within the repository
     * @param commentId      the ID of the comment to update
     * @param commentVersion the expected version of the comment
     * @param commentText    the new text of the comment
     * @return the updated comment
     * @throws CommentOutOfDateException if the version of the specified comment does not match the expected version
     * @throws NoSuchCommentException if no comment exists with the specified ID
     */
    @Nonnull
    Comment updateComment(int repositoryId, long pullRequestId, long commentId, int commentVersion,
                          @Nonnull String commentText);

    /**
     * Adds the current user as a watcher of the given pull request.
     *
     * @param repositoryId  the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @return the created watcher
     * @throws NoSuchPullRequestException if no pull request with the specified ID can be found in the repository
     */
    @Nonnull
    Watcher watch(int repositoryId, long pullRequestId);

    /**
     * Allows the current user to withdraw their approval of a pull request. The user must already be a participant of the
     * pull request else an exception is thrown. If the user has already withdrawn their approval this operation
     * has no effect. A participant may only withdraw approval for a pull request if it open.
     * <p>
     * Since 4.2 authors may not have their status changed and non-participants may not withdraw approvals.
     *
     * @param repositoryId  the ID of the repository to which the pull request will be merged
     * @param pullRequestId the ID of the pull request within the repository
     * @return the updated participant entity
     * @throws NoSuchParticipantException if the user is not a participant in the pull request and thus cannot
     *         withdraw their approval
     * @throws IllegalPullRequestStateException if the pull request is not open
     * @deprecated in 4.2 for removal in 5.0. Use {@link #setReviewerStatus} instead.
     */
    @Nonnull
    @Deprecated
    PullRequestParticipant withdrawApproval(int repositoryId, long pullRequestId);
}
