'use strict';

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

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;
}

function ownKeys(object, enumerableOnly) {
  var keys = Object.keys(object);

  if (Object.getOwnPropertySymbols) {
    var symbols = Object.getOwnPropertySymbols(object);
    if (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 = arguments[i] != null ? arguments[i] : {};

    if (i % 2) {
      ownKeys(Object(source), true).forEach(function (key) {
        _defineProperty(target, key, source[key]);
      });
    } else if (Object.getOwnPropertyDescriptors) {
      Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
    } else {
      ownKeys(Object(source)).forEach(function (key) {
        Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
      });
    }
  }

  return target;
}

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", _objectSpread2({
    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
}) {
  var _branchInfo$repositor, _branchInfo$repositor2, _branchInfo$repositor3, _branchInfo$repositor4;

  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
  };

  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}", _objectSpread2({
      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;

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