import util from 'util'
import childProcess from 'child_process'
const exec = util.promisify(childProcess.exec)
import { getGitDiffsFromPatchText} from '../../helpers.ts'
import { type Repository } from '../../dataTypes.ts'

export const getFileList = async (branchName: string, repoLocation: string) => {
  const command = `git -C ${repoLocation} ls-tree -r --name-only ${branchName}`

  const result = await exec(command)
  let files = result.stdout.split("\n").filter(item => item.length > 0 && item != ".")
  // TODO: this could be better. This is adding each sub-path of a file to a set, so that
  // we don't wind up with repeats, and then converting that back into an array. E.g. if
  // we have two files:
  // - posts/blog/one.md
  // - posts/blog/two.md
  // this will add the following to the set:
  // posts, posts/blog, posts/blog/one.md, posts, posts/blog, posts/blog/two.md
  // The repeats will be omitted because it's a Set, and the resulting array will
  // be [posts, posts/blog, posts/blog/one.md, posts/blog/two.md].
  // This is because it's convenient to have the directories show up as their own "file"
  // in the file list, even though git doesn't treat them that way.
  const fileSet: Set<string> = new Set()
  files.forEach((file) => {
    const fileParts = file.split("/")
    const allPathsInFile = fileParts.reduce((accumulator, currentValue, index) => {
      // Skip the first iteration, we have already added it as the initialValue
      if (index === 0) { return accumulator }

      accumulator.push(accumulator[accumulator.length - 1] + '/' + currentValue)
      return accumulator
    }, [fileParts[0]])

    allPathsInFile.forEach((path) => {
      fileSet.add(path)
    })
  })
  return Array.from(fileSet)
}

export const addBranchToCommitsMap = async(branchName: string, repoLocation: string, commits: Repository['commits']): Promise<void> => {
  const totalPatchesCountRes = await exec(`git -C ${repoLocation} rev-list --count ${branchName}`)
  const totalPatchesCount = parseInt(totalPatchesCountRes.stdout)
  let previousHash: null | string = null
  for (let i = 0; i < totalPatchesCount; i = i + 10) {
    const gitLogSubsetRes = await exec(`git -C ${repoLocation} log ${branchName} -p -n 10 --skip ${i}`)
    let gitLogSubset = gitLogSubsetRes.stdout.split("\n")
    do {
      const nextPatchStart = gitLogSubset.findIndex((line, index) => {
        return (index > 0 && line.startsWith("commit ")) || index === gitLogSubset.length - 1
      })
      const isEndOfFile = nextPatchStart === gitLogSubset.length - 1
      const currentPatch = gitLogSubset.slice(0, isEndOfFile ? nextPatchStart : nextPatchStart - 1)
      gitLogSubset = gitLogSubset.slice(nextPatchStart)

      const hash = currentPatch[0].replace("commit ", "").trim()

      // set the parent hash of the previous commit, unless we're on the first commit
      if (previousHash !== null) {
        commits.get(previousHash)['parent'] = hash
      }
      previousHash = hash

      // exit early if the commits map already includes this hash
      if (commits.has(hash)) {
        return
      }

      let author: string, date: string, isMerge: boolean
      [1, 2, 3].forEach((lineNumber) => {
        if (currentPatch[lineNumber].startsWith("Author")) {
          author = currentPatch[lineNumber].replace("Author: ", "").trim()
        }
        else if (currentPatch[lineNumber].startsWith("Date")) {
          date = currentPatch[lineNumber].replace("Date: ", "").trim()
        }
        else if (currentPatch[lineNumber].startsWith("Merge")) {
          isMerge = true
        }
      })
      let diffStart = currentPatch.findIndex((line) => {
        return line.startsWith("diff ")
      })
      // If no line starts with "diff", this
      // is probably a mege commit. Use the last
      // line of the patch + 1, in that case, to just get the full
      // text of the commit
      let messageStart = 4
      if (diffStart === -1) {
        messageStart = 5
        diffStart = currentPatch.length
      }
      // Git log is indent four spaces by default -- remove those.
      const commitMessage = currentPatch.slice(messageStart, diffStart).map(str => str.replace("    ", "")).filter((line) => {
        // git log --porcelain output adds these "Ignore-this:" lines and I'm not sure what they are
        return !line.startsWith('Ignore-this: ')
      }).join("\n").trim()

      const diffs = getGitDiffsFromPatchText(currentPatch.slice(diffStart).join("\n"))
      commits.set(hash, {
        hash,
        message: commitMessage,
        isMerge: isMerge || false,
        author,
        date: new Date(date),
        diffs,
        parent: null,
      })
    } while (gitLogSubset.length > 1)
  }
}

export const getFileLastTouchInfo = async (branch: string, filename: string, repoLocation: string) => {
  const regex = RegExp(".* [0-9]+ [0-9]+")
  const command = `git -C ${repoLocation} blame --porcelain ${branch} ${filename}`
  const res = await exec(command)
  const output = res.stdout
  const outputLines = output.split("\n")
  const initialValue: Array<Array<string>> = [[outputLines[0]]]
  const chunked = outputLines.reduce((accumulator, currentLine, currentIndex) => {
    // skip the first iteration since we already gave it initialValue
    if (currentIndex == 0) { return accumulator }

    if (currentLine.match(regex)) {
      accumulator.push([currentLine])
      return accumulator
    }
    else {
      accumulator[accumulator.length - 1].push(currentLine)
      return accumulator
    }
  }, initialValue)

  let currentAuthor = ''
  let authorsAndShasByLine = []
  chunked.forEach((chunk) => {
    let shaAndLineNumberParts = chunk[0].split(' ')
    let line = parseInt(shaAndLineNumberParts[2])
    let sha = shaAndLineNumberParts[0]
    if (chunk[1] && chunk[1].startsWith('author ')) {
      currentAuthor = chunk[1].replace('author ', '')
    }
    authorsAndShasByLine[line - 1] = {sha, author: currentAuthor}
  })
  return authorsAndShasByLine
}
