async function valueToTreeObject(octokit, owner, repo, path, value) {
    let mode = "100644";
    if (value !== null && typeof value !== "string") {
        mode = value.mode || mode;
    }
    // Text files can be changed through the .content key
    if (typeof value === "string") {
        return {
            path,
            mode: mode,
            content: value,
        };
    }
    // 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,
    };
}

async function createTree(state, changes) {
    const { octokit, owner, repo, ownerOrFork, latestCommitSha, latestCommitTreeSha, } = state;
    const tree = (await Promise.all(Object.keys(changes.files).map(async (path) => {
        const value = changes.files[path];
        if (value === null) {
            // 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,
                });
                return {
                    path,
                    mode: "100644",
                    sha: null,
                };
            }
            catch (error) {
                return;
            }
        }
        // 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 }));
            }
            catch (error) {
                // istanbul ignore if
                if (error.status !== 404)
                    throw error;
                // @ts-ignore
                result = await value({ exists: false });
            }
            if (result === null || typeof result === "undefined")
                return;
            return valueToTreeObject(octokit, ownerOrFork, repo, path, result);
        }
        return valueToTreeObject(octokit, ownerOrFork, repo, path, value);
    }))).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;
    // 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,
        message,
        tree: state.latestCommitTreeSha,
        parents: [latestCommitSha],
    });
    return latestCommit.sha;
}

async function composeCreatePullRequest(octokit, { owner, repo, title, body, base, head, createWhenEmpty, changes: changesOption, draft = false, forceFork = false, update = false, }) {
    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,
    };
    if (existingPullRequest) {
        // https://docs.github.com/en/rest/pulls/pulls#update-a-pull-request
        return 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
        return await octokit.request("POST /repos/{owner}/{repo}/pulls", pullRequestOptions);
    }
}

const VERSION = "3.13.1";

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

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