async function valueToTreeObject(octokit, owner, repo, path, value) {
    const defaultMode = "100644";
    // Text files can be changed through the .content key
    if (typeof value === "string") {
        return {
            path,
            mode: defaultMode,
            content: value,
        };
    }
    const mode = value.mode ?? defaultMode;
    // UTF-8 files can be treated as text files
    // https://github.com/gr2m/octokit-plugin-create-pull-request/pull/133
    if (value.encoding === "utf-8") {
        return {
            path,
            mode: mode,
            content: value.content,
        };
    }
    // Binary files need to be created first using the git blob API,
    // then changed by referencing in the .sha key
    const { data } = await octokit.request("POST /repos/{owner}/{repo}/git/blobs", {
        owner,
        repo,
        ...value,
    });
    const blobSha = data.sha;
    return {
        path,
        mode: mode,
        sha: blobSha,
    };
}

const DELETE_FILE = Symbol("DELETE_FILE");

async function createTree(state, changes) {
    const { octokit, owner, repo, ownerOrFork, latestCommitSha, latestCommitTreeSha, } = state;
    let tree = [];
    for (const path of Object.keys(changes.files)) {
        const value = changes.files[path];
        if (value === DELETE_FILE) {
            // Deleting a non-existent file from a tree leads to an "GitRPC::BadObjectState" error,
            // so we only attempt to delete the file if it exists.
            try {
                // https://developer.github.com/v3/repos/contents/#get-contents
                await octokit.request("HEAD /repos/{owner}/{repo}/contents/:path", {
                    owner: ownerOrFork,
                    repo,
                    ref: latestCommitSha,
                    path,
                });
                tree.push({
                    path,
                    mode: "100644",
                    sha: null,
                });
                continue;
            }
            catch (error) {
                continue;
            }
        }
        // When passed a function, retrieve the content of the file, pass it
        // to the function, then return the result
        if (typeof value === "function") {
            let result;
            try {
                const { data: file } = await octokit.request("GET /repos/{owner}/{repo}/contents/:path", {
                    owner: ownerOrFork,
                    repo,
                    ref: latestCommitSha,
                    path,
                });
                result = await value(Object.assign(file, { exists: true }));
                if (result === DELETE_FILE) {
                    try {
                        // https://developer.github.com/v3/repos/contents/#get-contents
                        await octokit.request("HEAD /repos/{owner}/{repo}/contents/:path", {
                            owner: ownerOrFork,
                            repo,
                            ref: latestCommitSha,
                            path,
                        });
                        tree.push({
                            path,
                            mode: "100644",
                            sha: null,
                        });
                        continue;
                    }
                    catch (error) {
                        // istanbul ignore next
                        continue;
                    }
                }
            }
            catch (error) {
                // @ts-ignore
                // istanbul ignore if
                if (error.status !== 404)
                    throw error;
                // @ts-ignore
                result = await value({ exists: false });
            }
            if (result === null ||
                typeof result === "undefined" ||
                typeof result === "symbol") {
                continue;
            }
            tree.push(
            // @ts-expect-error - Argument result can never be of type Symbol at this branch
            // because the above condition will catch it and move on to the next iteration cycle
            await valueToTreeObject(octokit, ownerOrFork, repo, path, result));
            continue;
        }
        // @ts-expect-error - Argument value can never be of type Symbol at this branch
        // because the above condition will catch it and initiate a file deletion operation
        tree.push(await valueToTreeObject(octokit, ownerOrFork, repo, path, value));
        continue;
    }
    tree = tree.filter(Boolean);
    if (tree.length === 0) {
        return null;
    }
    // https://developer.github.com/v3/git/trees/#create-a-tree
    const { data: { sha: newTreeSha }, } = await octokit.request("POST /repos/{owner}/{repo}/git/trees", {
        owner: ownerOrFork,
        repo,
        base_tree: latestCommitTreeSha,
        tree,
    });
    return newTreeSha;
}

async function createCommit(state, treeCreated, changes) {
    const { octokit, repo, ownerOrFork, latestCommitSha } = state;
    const message = treeCreated
        ? changes.commit
        : typeof changes.emptyCommit === "string"
            ? changes.emptyCommit
            : changes.commit;
    const commit = {
        message,
        author: changes.author,
        committer: changes.committer,
        tree: state.latestCommitTreeSha,
        parents: [latestCommitSha],
    };
    // https://developer.github.com/v3/git/commits/#create-a-commit
    const { data: latestCommit } = await octokit.request("POST /repos/{owner}/{repo}/git/commits", {
        owner: ownerOrFork,
        repo,
        ...commit,
        signature: changes.signature
            ? await changes.signature(commit)
            : undefined,
    });
    return latestCommit.sha;
}

async function composeCreatePullRequest(octokit, { owner, repo, title, body, base, head, createWhenEmpty, changes: changesOption, draft = false, labels = [], forceFork = false, update = false, }) {
    if (head === base) {
        throw new Error('[octokit-plugin-create-pull-request] "head" cannot be the same value as "base"');
    }
    const changes = Array.isArray(changesOption)
        ? changesOption
        : [changesOption];
    if (changes.length === 0)
        throw new Error('[octokit-plugin-create-pull-request] "changes" cannot be an empty array');
    const state = { octokit, owner, repo };
    // https://developer.github.com/v3/repos/#get-a-repository
    const { data: repository, headers } = await octokit.request("GET /repos/{owner}/{repo}", {
        owner,
        repo,
    });
    const isUser = !!headers["x-oauth-scopes"];
    if (!repository.permissions) {
        throw new Error("[octokit-plugin-create-pull-request] Missing authentication");
    }
    if (!base) {
        base = repository.default_branch;
    }
    state.ownerOrFork = owner;
    if (forceFork || (isUser && !repository.permissions.push)) {
        // https://developer.github.com/v3/users/#get-the-authenticated-user
        const user = await octokit.request("GET /user");
        // https://developer.github.com/v3/repos/forks/#list-forks
        const forks = await octokit.request("GET /repos/{owner}/{repo}/forks", {
            owner,
            repo,
        });
        const hasFork = forks.data.find(
        /* istanbul ignore next - fork owner can be null, but we don't test that */
        (fork) => fork.owner && fork.owner.login === user.data.login);
        if (!hasFork) {
            // https://developer.github.com/v3/repos/forks/#create-a-fork
            await octokit.request("POST /repos/{owner}/{repo}/forks", {
                owner,
                repo,
            });
        }
        state.ownerOrFork = user.data.login;
    }
    // https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository
    const { data: [latestCommit], } = await octokit.request("GET /repos/{owner}/{repo}/commits", {
        owner,
        repo,
        sha: base,
        per_page: 1,
    });
    state.latestCommitSha = latestCommit.sha;
    state.latestCommitTreeSha = latestCommit.commit.tree.sha;
    const baseCommitTreeSha = latestCommit.commit.tree.sha;
    for (const change of changes) {
        let treeCreated = false;
        if (change.files && Object.keys(change.files).length) {
            const latestCommitTreeSha = await createTree(state, change);
            if (latestCommitTreeSha) {
                state.latestCommitTreeSha = latestCommitTreeSha;
                treeCreated = true;
            }
        }
        if (treeCreated || change.emptyCommit !== false) {
            state.latestCommitSha = await createCommit(state, treeCreated, change);
        }
    }
    const hasNoChanges = baseCommitTreeSha === state.latestCommitTreeSha;
    if (hasNoChanges && createWhenEmpty === false) {
        return null;
    }
    const branchInfo = await octokit.graphql(`
    query ($owner: String!, $repo: String!, $head: String!) {
      repository(name: $repo, owner: $owner) {
        ref(qualifiedName: $head) {
          associatedPullRequests(first: 1, states: OPEN) {
            edges {
              node {
                id
                number
                url
              }
            }
          }
        }
      }
    }`, {
        owner: state.ownerOrFork,
        repo,
        head,
    });
    const branchExists = !!branchInfo.repository.ref;
    const existingPullRequest = branchInfo.repository.ref?.associatedPullRequests?.edges?.[0]?.node;
    if (existingPullRequest && !update) {
        throw new Error(`[octokit-plugin-create-pull-request] Pull request already exists: ${existingPullRequest.url}. Set update=true to enable updating`);
    }
    if (branchExists) {
        // https://docs.github.com/en/rest/git/refs#update-a-reference
        await octokit.request("PATCH /repos/{owner}/{repo}/git/refs/{ref}", {
            owner: state.ownerOrFork,
            repo,
            sha: state.latestCommitSha,
            ref: `heads/${head}`,
            force: true,
        });
    }
    else {
        // https://developer.github.com/v3/git/refs/#create-a-reference
        await octokit.request("POST /repos/{owner}/{repo}/git/refs", {
            owner: state.ownerOrFork,
            repo,
            sha: state.latestCommitSha,
            ref: `refs/heads/${head}`,
        });
    }
    const pullRequestOptions = {
        owner,
        repo,
        head: `${state.ownerOrFork}:${head}`,
        base,
        title,
        body,
        draft,
    };
    let res;
    if (existingPullRequest) {
        // https://docs.github.com/en/rest/pulls/pulls#update-a-pull-request
        res = await octokit.request("PATCH /repos/{owner}/{repo}/pulls/{pull_number}", {
            pull_number: existingPullRequest.number,
            ...pullRequestOptions,
        });
    }
    else {
        // https://developer.github.com/v3/pulls/#create-a-pull-request
        res = await octokit.request("POST /repos/{owner}/{repo}/pulls", pullRequestOptions);
    }
    if (labels.length) {
        try {
            const labelRes = await octokit.request("POST /repos/{owner}/{repo}/issues/{number}/labels", {
                owner,
                repo,
                number: res.data.number,
                labels,
            });
            // istanbul ignore if
            if (labelRes.data.length > labels.length) {
                octokit.log.warn("The pull request already contains more labels than the ones provided. This could be due to the presence of previous labels.");
            }
        }
        catch (error) {
            // @ts-ignore
            // istanbul ignore if
            if (error.status === 403) {
                octokit.log.warn("You do not have permissions to apply labels to this pull request. However, the pull request has been successfully created without the requested labels.");
                return res;
            }
            // @ts-ignore
            if (error.status !== 403)
                throw error;
        }
    }
    return res;
}

const VERSION = "5.1.1";

/**
 * @param octokit Octokit instance
 */
function createPullRequest(octokit) {
    return {
        createPullRequest: composeCreatePullRequest.bind(null, octokit),
    };
}
createPullRequest.VERSION = VERSION;

export { DELETE_FILE, composeCreatePullRequest, createPullRequest };
//# sourceMappingURL=index.js.map
