'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

function ownKeys(object, enumerableOnly) {
  var keys = Object.keys(object);
  if (Object.getOwnPropertySymbols) {
    var symbols = Object.getOwnPropertySymbols(object);
    enumerableOnly && (symbols = symbols.filter(function (sym) {
      return Object.getOwnPropertyDescriptor(object, sym).enumerable;
    })), keys.push.apply(keys, symbols);
  }
  return keys;
}
function _objectSpread2(target) {
  for (var i = 1; i < arguments.length; i++) {
    var source = null != arguments[i] ? arguments[i] : {};
    i % 2 ? ownKeys(Object(source), !0).forEach(function (key) {
      _defineProperty(target, key, source[key]);
    }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) {
      Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
    });
  }
  return target;
}
function _defineProperty(obj, key, value) {
  if (key in obj) {
    Object.defineProperty(obj, key, {
      value: value,
      enumerable: true,
      configurable: true,
      writable: true
    });
  } else {
    obj[key] = value;
  }
  return obj;
}

async function valueToTreeObject(octokit, owner, repo, path, value) {
  var _value$mode;
  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 = value.mode) !== null && _value$mode !== void 0 ? _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", _objectSpread2({
    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", _objectSpread2(_objectSpread2({
    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
}) {
  var _branchInfo$repositor, _branchInfo$repositor2, _branchInfo$repositor3, _branchInfo$repositor4;
  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$repositor = branchInfo.repository.ref) === null || _branchInfo$repositor === void 0 ? void 0 : (_branchInfo$repositor2 = _branchInfo$repositor.associatedPullRequests) === null || _branchInfo$repositor2 === void 0 ? void 0 : (_branchInfo$repositor3 = _branchInfo$repositor2.edges) === null || _branchInfo$repositor3 === void 0 ? void 0 : (_branchInfo$repositor4 = _branchInfo$repositor3[0]) === null || _branchInfo$repositor4 === void 0 ? void 0 : _branchInfo$repositor4.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}", _objectSpread2({
      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;

exports.DELETE_FILE = DELETE_FILE;
exports.composeCreatePullRequest = composeCreatePullRequest;
exports.createPullRequest = createPullRequest;
//# sourceMappingURL=index.js.map
