text: /** @babel */
  
  import {shell} from 'electron'
  import {Range} from 'atom'
  import {parse as parseURL} from 'url'
  import path from 'path'
  
  export default class GitHubFile {
    // Public
    static fromPath (filePath) {
      return new GitHubFile(filePath)
    }
  
    constructor (filePath) {
      this.filePath = filePath
      const [rootDir] = atom.project.relativizePath(this.filePath)
  
      if (rootDir != null) {
        const rootDirIndex = atom.project.getPaths().indexOf(rootDir)
        this.repo = atom.project.getRepositories()[rootDirIndex]
        this.type = 'none'
        if (this.repo && this.gitURL()) {
          if (this.isGitHubWikiURL(this.githubRepoURL())) {
            this.type = 'wiki'
          } else if (this.isGistURL(this.githubRepoURL())) {
            this.type = 'gist'
          } else {
            this.type = 'repo'
          }
        }
      }
    }
  
    // Public
    open (lineRange) {
      if (this.validateRepo()) {
        this.openURLInBrowser(this.blobURL() + this.getLineRangeSuffix(lineRange))
      }
    }
  
    // Public
    openOnMaster (lineRange) {
      if (this.validateRepo()) {
        this.openURLInBrowser(this.blobURLForMaster() + this.getLineRangeSuffix(lineRange))
      }
    }
  
    // Public
    blame (lineRange) {
      if (this.validateRepo()) {
        if (this.type === 'repo') {
          this.openURLInBrowser(this.blameURL() + this.getLineRangeSuffix(lineRange))
        } else {
          atom.notifications.addWarning(`Blames do not exist for ${this.type}s`)
        }
      }
    }
  
    history () {
      if (this.validateRepo()) {
        this.openURLInBrowser(this.historyURL())
      }
    }
  
    copyURL (lineRange) {
      if (this.validateRepo()) {
        atom.clipboard.write(this.shaURL() + this.getLineRangeSuffix(lineRange))
      }
    }
  
    openBranchCompare () {
      if (this.validateRepo()) {
        if (this.type === 'repo') {
          this.openURLInBrowser(this.branchCompareURL())
        } else {
          atom.notifications.addWarning(`Branches do not exist for ${this.type}s`)
        }
      }
    }
  
    openIssues () {
      if (this.validateRepo()) {
        if (this.type === 'repo') {
          this.openURLInBrowser(this.issuesURL())
        } else {
          atom.notifications.addWarning(`Issues do not exist for ${this.type}s`)
        }
      }
    }
  
    openPullRequests () {
      if (this.validateRepo()) {
        if (this.type === 'repo') {
          this.openURLInBrowser(this.pullRequestsURL())
        } else {
          atom.notifications.addWarning(`Pull requests do not exist for ${this.type}s`)
        }
      }
    }
  
    openRepository () {
      if (this.validateRepo()) {
        this.openURLInBrowser(this.githubRepoURL())
      }
    }
  
    getLineRangeSuffix (lineRange) {
      if (lineRange && this.type !== 'wiki' && atom.config.get('open-on-github.includeLineNumbersInUrls')) {
        lineRange = Range.fromObject(lineRange)
        const startRow = lineRange.start.row + 1
        const endRow = lineRange.end.row + 1
  
        if (startRow === endRow) {
          if (this.type === 'gist') {
            return `-L${startRow}`
          } else {
            return `#L${startRow}`
          }
        } else {
          if (this.type === 'gist') {
            return `-L${startRow}-L${endRow}`
          } else {
            return `#L${startRow}-L${endRow}`
          }
        }
      } else {
        return ''
      }
    }
  
    // Internal
    validateRepo () {
      if (!this.repo) {
        atom.notifications.addWarning(`No repository found for path: ${this.filePath}.`)
        return false
      } else if (!this.gitURL()) {
        atom.notifications.addWarning(`No URL defined for remote: ${this.remoteName()}`)
        return false
      } else if (!this.githubRepoURL()) {
        atom.notifications.addWarning(`Remote URL is not hosted on GitHub: ${this.gitURL()}`)
        return false
      }
      return true
    }
  
    // Internal
    openURLInBrowser (url) {
      shell.openExternal(url)
    }
  
    // Internal
    blobURL () {
      const gitHubRepoURL = this.githubRepoURL()
      const repoRelativePath = this.repoRelativePath()
  
      if (this.type === 'wiki') {
        return `${gitHubRepoURL}/${this.extractFileName(repoRelativePath)}`
      } else if (this.type === 'gist') {
        return `${gitHubRepoURL}#file-${this.encodeSegments(repoRelativePath.replace(/\./g, '-'))}`
      } else {
        return `${gitHubRepoURL}/blob/${this.remoteBranchName()}/${this.encodeSegments(repoRelativePath)}`
      }
    }
  
    // Internal
    blobURLForMaster () {
      const gitHubRepoURL = this.githubRepoURL()
  
      if (this.type === 'repo') {
        return `${gitHubRepoURL}/blob/master/${this.encodeSegments(this.repoRelativePath())}`
      } else {
        return this.blobURL() // Only repos have branches
      }
    }
  
    // Internal
    shaURL () {
      const gitHubRepoURL = this.githubRepoURL()
      const encodedSHA = this.encodeSegments(this.sha())
      const repoRelativePath = this.repoRelativePath()
  
      if (this.type === 'wiki') {
        return `${gitHubRepoURL}/${this.extractFileName(repoRelativePath)}/${encodedSHA}`
      } else if (this.type === 'gist') {
        return `${gitHubRepoURL}/${encodedSHA}#file-${this.encodeSegments(repoRelativePath.replace(/\./g, '-'))}`
      } else {
        return `${gitHubRepoURL}/blob/${encodedSHA}/${this.encodeSegments(repoRelativePath)}`
      }
    }
  
    // Internal
    blameURL () {
      return `${this.githubRepoURL()}/blame/${this.remoteBranchName()}/${this.encodeSegments(this.repoRelativePath())}`
    }
  
    // Internal
    historyURL () {
      const gitHubRepoURL = this.githubRepoURL()
  
      if (this.type === 'wiki') {
        return `${gitHubRepoURL}/${this.extractFileName(this.repoRelativePath())}/_history`
      } else if (this.type === 'gist') {
        return `${gitHubRepoURL}/revisions`
      } else {
        return `${gitHubRepoURL}/commits/${this.remoteBranchName()}/${this.encodeSegments(this.repoRelativePath())}`
      }
    }
  
    // Internal
    issuesURL () {
      return `${this.githubRepoURL()}/issues`
    }
  
    // Internal
    pullRequestsURL () {
      return `${this.githubRepoURL()}/pulls`
    }
  
    // Internal
    branchCompareURL () {
      return `${this.githubRepoURL()}/compare/${this.encodeSegments(this.branchName())}`
    }
  
    encodeSegments (segments = '') {
      return segments.split('/').map(segment => encodeURIComponent(segment)).join('/')
    }
  
    // Internal
    extractFileName (relativePath = '') {
      return path.parse(relativePath).name
    }
  
    // Internal
    gitURL () {
      const remoteName = this.remoteName()
      if (remoteName != null) {
        return this.repo.getConfigValue(`remote.${remoteName}.url`, this.filePath)
      } else {
        return this.repo.getConfigValue(`remote.origin.url`, this.filePath)
      }
    }
  
    // Internal
    githubRepoURL () {
      let url = this.gitURL()
  
      if (url.match(/git@[^:]+:/)) { // git@github.com:user/repo.git
        url = url.replace(/^git@([^:]+):(.+)$/, (match, host, repoPath) => {
          repoPath = repoPath.replace(/^\/+/, '')
          return `https://${host}/${repoPath}` // -> https://github.com/user/repo.git
        })
      } else if (url.match(/^ssh:\/\/git@([^/]+)\//)) { // ssh://git@github.com/user/repo.git
        url = `https://${url.substring(10)}` // -> https://github.com/user/repo.git
      } else if (url.match(/^git:\/\/[^/]+\//)) { // git://github.com/user/repo.git
        url = `https${url.substring(3)}` // -> https://github.com/user/repo.git
      } else if (url.match(/^https?:\/\/\w+@/)) { // https://user@github.com/user/repo.git
        url = url.replace(/^https?:\/\/\w+@/, 'https://') // -> https://github.com/user/repo.git
      }
  
      // Remove trailing .git and trailing slashes
      url = url.replace(/\.git$/, '').replace(/\/+$/, '')
      // Change .wiki to /wiki
      url = url.replace(/\.wiki$/, '/wiki')
  
      if (!this.isBitbucketURL(url)) {
        return url
      }
    }
  
    isGistURL (url) {
      try {
        const {host} = parseURL(url)
  
        return host === 'gist.github.com'
      } catch (error) {
        return false
      }
    }
  
    isGitHubWikiURL (url) {
      return /\/wiki$/.test(url)
    }
  
    isBitbucketURL (url) {
      if (url.startsWith('git@bitbucket.org')) {
        return true
      }
  
      try {
        const {host} = parseURL(url)
  
        return host === 'bitbucket.org'
      } catch (error) {
        return false
      }
    }
  
    // Internal
    repoRelativePath () {
      return this.repo.getRepo(this.filePath).relativize(this.filePath)
    }
  
    // Internal
    remoteName () {
      const gitConfigRemote = this.repo.getConfigValue('atom.open-on-github.remote', this.filePath)
  
      if (gitConfigRemote) {
        return gitConfigRemote
      }
  
      const shortBranch = this.repo.getShortHead(this.filePath)
  
      if (!shortBranch) {
        return null
      }
  
      const branchRemote = this.repo.getConfigValue(`branch.${shortBranch}.remote`, this.filePath)
  
      if (branchRemote && branchRemote.length > 0) {
        return branchRemote
      }
  
      return null
    }
  
    // Internal
    sha () {
      return this.repo.getReferenceTarget('HEAD', this.filePath)
    }
  
    // Internal
    branchName () {
      const shortBranch = this.repo.getShortHead(this.filePath)
  
      if (!shortBranch) {
        return null
      }
  
      const branchMerge = this.repo.getConfigValue(`branch.${shortBranch}.merge`, this.filePath)
      if (!(branchMerge && branchMerge.length > 11)) {
        return shortBranch
      }
  
      if (branchMerge.indexOf('refs/heads/') !== 0) {
        return shortBranch
      }
  
      return branchMerge.substring(11)
    }
  
    // Internal
    remoteBranchName () {
      const gitConfigBranch = this.repo.getConfigValue('atom.open-on-github.branch', this.filePath)
  
      if (gitConfigBranch) {
        return gitConfigBranch
      } else if (this.remoteName() != null) {
        return this.encodeSegments(this.branchName())
      } else {
        return 'master'
      }
    }
  }
  