import pify from 'pify';
import path from 'path';

var fs;
var fs$1 = function () {
  return fs;
};
function setfs(newFs) {
  fs = newFs;
}

async function rm(filepath) {
  try {
    await pify(fs$1().unlink)(filepath);
  } catch (err) {
    if (err.code !== 'ENOENT') throw err;
  }
}

// An async exists variant
async function exists(file, options) {
  return new Promise(function (resolve, reject) {
    fs$1().stat(file, (err, stats) => {
      if (err) return err.code === 'ENOENT' ? resolve(false) : reject(err);
      resolve(true);
    });
  });
}

// @flow
/*::
type Node = {
  type: string,
  fullpath: string,
  basename: string,
  metadata: Object, // mode, oid
  parent?: Node,
  children: Array<Node>
}
*/

function flatFileListToDirectoryStructure(files /*: Array<{path: string}> */
) /*: Node|void */{
  const inodes /*: Map<string, Node> */ = new Map();
  const mkdir = function (name) /*: Node|void */{
    if (!inodes.has(name)) {
      let dir /*: Node */ = {
        type: 'tree',
        fullpath: name,
        basename: path.basename(name),
        metadata: {},
        children: []
      };
      inodes.set(name, dir);
      // This recursively generates any missing parent folders.
      // We do it after we've added the inode to the set so that
      // we don't recurse infinitely trying to create the root '.' dirname.
      dir.parent = mkdir(path.dirname(name));
      if (dir.parent && dir.parent !== dir) dir.parent.children.push(dir);
    }
    return inodes.get(name);
  };

  const mkfile = function (name, metadata) /*: Node|void */{
    if (!inodes.has(name)) {
      let file /*: Node */ = {
        type: 'blob',
        fullpath: name,
        basename: path.basename(name),
        metadata: metadata,
        // This recursively generates any missing parent folders.
        parent: mkdir(path.dirname(name)),
        children: []
      };
      if (file.parent) file.parent.children.push(file);
      inodes.set(name, file);
    }
    return inodes.get(name);
  };

  for (let file of files) {
    mkfile(file.path, file);
  }
  return inodes.get('.');
}

async function sleep(ms) {
  return new Promise((resolve, reject) => setTimeout(resolve, ms));
}

// @flow
// This is modeled after the lockfile strategy used by the git source code.
const delayedReleases = new Map();

async function lock(filename /*: string */
, triesLeft /*: number */ = 3) {
  // check to see if we still have it
  if (delayedReleases.has(filename)) {
    clearTimeout(delayedReleases.get(filename));
    delayedReleases.delete(filename);
    return;
  }
  if (triesLeft === 0) {
    throw new Error(`Unable to acquire lockfile '${filename}'. Exhausted tries.`);
  }
  try {
    await pify(fs$1().mkdir)(`${filename}.lock`);
  } catch (err) {
    if (err.code === 'EEXIST') {
      await sleep(100);
      await lock(filename, triesLeft - 1);
    }
  }
}

async function unlock(filename /*: string */
, delayRelease /*: number */ = 50) {
  if (delayedReleases.has(filename)) {
    throw new Error('Cannot double-release lockfile');
  }
  // Basically, we lie and say it was deleted ASAP.
  // But really we wait a bit to see if you want to acquire it again.
  delayedReleases.set(filename, setTimeout(async () => {
    delayedReleases.delete(filename);
    await pify(fs$1().rmdir)(`${filename}.lock`);
  }));
}

// @flow
async function mkdir(dirpath /*: string */) {
  try {
    await pify(fs$1().mkdir)(dirpath);
    return;
  } catch (err) {
    // If err is null then operation succeeded!
    if (err === null) return;
    // If the directory already exists, that's OK!
    if (err.code === 'EEXIST') return;
    // If we got a "no such file or directory error" backup and try again.
    if (err.code === 'ENOENT') {
      let parent = path.dirname(dirpath);
      // Check to see if we've gone too far
      if (parent === '.' || parent === '/' || parent === dirpath) throw err;
      // Infinite recursion, what could go wrong?
      await mkdir(parent);
      await mkdir(dirpath);
    }
  }
}

async function mkdirs(dirlist /*: string[] */) {
  return Promise.all(dirlist.map(mkdir));
}

// An async readFile variant that returns null instead of throwing errors
async function read(file, options) {
  return new Promise(function (resolve, reject) {
    fs$1().readFile(file, options, (err, file) => err ? resolve(null) : resolve(file));
  });
}

// @flow
// An async writeFile variant that automatically creates missing directories,
// and returns null instead of throwing errors.
async function write(filepath /*: string */
, contents /*: string|Buffer */
, options /*: Object */ = {}) {
  try {
    await pify(fs$1().writeFile)(filepath, contents, options);
    return;
  } catch (err) {
    // Hmm. Let's try mkdirp and try again.
    await mkdir(path.dirname(filepath));
    await pify(fs$1().writeFile)(filepath, contents, options);
  }
}

var name = "isomorphic-git";
var version = "0.0.0-development";
var description = "A pure JavaScript implementation of git for node and browsers!";
var typings = "./src/index.d.ts";
var main = "dist/for-node/";
var browser = "dist/for-browserify/";
var module$1 = "dist/for-future/";
var unpkg = "dist/bundle.umd.min.js";
var bin = { "isogit": "./cli.js" };
var engines = { "node": ">=7.6.0" };
var scripts = { "start": "nps", "test": "nps test", "precommit": "nps format toc", "semantic-release": "semantic-release pre && npm publish && semantic-release post" };
var repository = { "type": "git", "url": "https://github.com/wmhilton/isomorphic-git.git" };
var keywords = ["git"];
var author = "William Hilton <wmhilton@gmail.com>";
var license = "Unlicense";
var bugs = { "url": "https://github.com/wmhilton/isomorphic-git/issues" };
var homepage = "https://github.com/wmhilton/isomorphic-git#readme";
var files = ["dist", "cli.js"];
var dependencies = { "async-lock": "^1.0.0", "await-stream-ready": "^1.0.1", "babel-runtime": "^6.26.0", "buffer": "^5.0.7", "buffer-peek-stream": "^1.0.1", "buffercursor": "0.0.12", "gartal": "^1.1.2", "git-apply-delta": "0.0.7", "git-list-pack": "0.0.10", "ignore": "^3.3.6", "ini": "^1.3.4", "marky": "^1.2.0", "minimisted": "^2.0.0", "openpgp": "^2.5.10", "pad": "^2.0.1", "pako": "^1.0.5", "pify": "^3.0.0", "shasum": "^1.0.2", "simple-concat": "^1.0.0", "simple-get": "^2.7.0", "through2": "^2.0.3" };
var devDependencies = { "babel-plugin-external-helpers": "^6.22.0", "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", "babel-plugin-transform-object-rest-spread": "^6.23.0", "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-env": "^1.6.0", "babel-preset-flow": "^6.23.0", "ban-sensitive-files": "^1.9.0", "browserfs": "^1.4.3", "browserify": "^14.4.0", "browserify-shim": "^3.8.14", "codecov": "^3.0.0", "doctoc": "^1.3.0", "esdoc": "^1.0.4", "esdoc-ecmascript-proposal-plugin": "^1.0.0", "esdoc-importpath-plugin": "^1.0.1", "esdoc-standard-plugin": "^1.0.0", "husky": "^0.14.3", "jest": "^21.2.1", "jest-fixtures": "^0.6.0", "jsonfile": "^4.0.0", "karma": "^1.7.1", "karma-browserify": "^5.1.1", "karma-chrome-launcher": "^2.2.0", "karma-firefox-launcher": "^1.0.1", "karma-sauce-launcher": "^1.2.0", "karma-tap": "^3.1.1", "lodash": "^4.17.4", "nock": "^9.0.17", "npm-run-all": "^4.1.1", "nps": "^5.7.1", "nps-utils": "^1.4.0", "parse-header-stream": "^1.1.1", "prettier-standard": "^7.0.3", "rollup": "^0.51.6", "rollup-plugin-babel": "^3.0.2", "rollup-plugin-json": "^2.3.0", "standard": "^10.0.3", "stream-equal": "^1.0.1", "tape": "^4.8.0", "uglify-es": "^3.1.2", "watch": "^1.0.2", "watchify": "^3.9.0", "semantic-release": "^8.2.0" };
var ava = { "source": ["dist/for-node/*"] };
var browserify = { "transform": ["browserify-shim"] };
var testling = { "files": "testling/basic-test.js", "browsers": ["chrome/latest", "firefox/latest", "ie/latest"] };
var jest = { "testPathIgnorePatterns": ["__helpers__"], "testEnvironment": "node" };
var _package = {
	name: name,
	version: version,
	description: description,
	typings: typings,
	main: main,
	browser: browser,
	module: module$1,
	unpkg: unpkg,
	bin: bin,
	engines: engines,
	scripts: scripts,
	repository: repository,
	keywords: keywords,
	author: author,
	license: license,
	bugs: bugs,
	homepage: homepage,
	files: files,
	dependencies: dependencies,
	devDependencies: devDependencies,
	ava: ava,
	browserify: browserify,
	testling: testling,
	jest: jest,
	"browserify-shim": { "fs": "global:fs" }
};

var _package$1 = Object.freeze({
	name: name,
	version: version,
	description: description,
	typings: typings,
	main: main,
	browser: browser,
	module: module$1,
	unpkg: unpkg,
	bin: bin,
	engines: engines,
	scripts: scripts,
	repository: repository,
	keywords: keywords,
	author: author,
	license: license,
	bugs: bugs,
	homepage: homepage,
	files: files,
	dependencies: dependencies,
	devDependencies: devDependencies,
	ava: ava,
	browserify: browserify,
	testling: testling,
	jest: jest,
	default: _package
});

/**
 * @param {string} company
 * @param {string} token
 * @returns {username, password}
 *
 * Use with {@link #gitpush .push} and {@link #gitpull .pull} to set Basic Authentication headers.
 * This for is for *actual* OAuth2 tokens (not "personal access tokens").
 * Unfortunately, all the major git hosting companies have chosen different conventions!
 * Lucky for you, I already looked up and codified it for you.
 *
 * - oauth2('github', token) - Github uses `token` as the username, and 'x-oauth-basic' as the password.
 * - oauth2('bitbucket', token) - Bitbucket uses 'x-token-auth' as the username, and `token` as the password.
 * - oauth2('gitlab', token) - Gitlab uses 'oauth2' as the username, and `token` as the password.
 *
 * I will gladly accept pull requests for more companies' conventions.
 */
function oauth2(company, token) {
  switch (company) {
    case 'github':
      return {
        username: token,
        password: 'x-oauth-basic'
      };
    case 'bitbucket':
      return {
        username: 'x-token-auth',
        password: token
      };
    case 'gitlab':
      return {
        username: 'oauth2',
        password: token
      };
    default:
      throw new Error(`I don't know how ${company} expects its Basic Auth headers to be formatted for OAuth2 usage. If you do, you can use the regular '.auth(username, password)' to set the basic auth header yourself.`);
  }
}

/**
 *
 * Use with {@link #gitpush .push} and {@link #gitpull .pull} to set Basic Authentication headers.
 * This works for basic username / password auth, or the newer username / token auth
 * that is often required if 2FA is enabled.
 *
 * Authentication is normally required for pushing to a git repository.
 * It may also be required to clone or fetch from a private repository.
 * Git does all its authentication using HTTPS Basic Authentication.
 * Usually this is straightforward, but there are some things to watch out for.
 *
 * If you have two-factor authentication (2FA) enabled on your account, you
 * probably cannot push or pull using your regular username and password.
 * Instead, you may have to create a Personal Access Token (or an App
 * Password in Bitbucket lingo) and use that to authenticate.
 *
 * @param {string} username
 * @param {string} password
 * @returns {username, password}
 *
 * @example
 * let {username, password} = auth('username', 'password')
 *
 * // a one-argument version is also supported
 * let {username, password} = auth('username:password')
 *
 * // Personal Access Token Authentication
 * // (note Bitbucket calls theirs App Passwords instead for some reason)
 * let {username, password} = auth('username', 'personal access token')
 * let {username, password} = auth('username', 'app password')
 * let {username, password} = auth('personal access token') // Github (only) lets you leave out the username
 */
function auth(username, password) {
  // Allow specifying it as one argument (mostly for CLI inputability)
  if (password === undefined) {
    let i = username.indexOf(':');
    if (i > -1) {
      password = username.slice(i + 1);
      username = username.slice(0, i);
    } else {
      password = ''; // Enables the .auth(GITHUB_TOKEN) no-username shorthand
    }
  }
  return { username, password };
}

export { rm, exists, flatFileListToDirectoryStructure, fs$1 as fs, setfs, lock, unlock, mkdirs, read, sleep, write, _package$1 as pkg, oauth2, auth };
