/* eslint-disable
    camelcase,
    max-len,
    no-return-assign,
    no-unused-vars,
*/
// TODO: This file was created by bulk-decaffeinate.
// Fix any style issues and re-enable lint.
/*
 * decaffeinate suggestions:
 * DS101: Remove unnecessary use of Array.from
 * DS102: Remove unnecessary code created because of implicit returns
 * DS206: Consider reworking classes to avoid initClass
 * DS207: Consider shorter variations of null checks
 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
 */
import moment from 'moment'
import ColorManager from '../colors/ColorManager'
import displayNameForUser from './util/displayNameForUser'
import './controllers/HistoryListController'
import './controllers/HistoryDiffController'
import './directives/infiniteScroll'
let HistoryManager

export default HistoryManager = (function () {
  HistoryManager = class HistoryManager {
    static initClass() {
      this.prototype.BATCH_SIZE = 10
    }

    constructor(ide, $scope) {
      this.ide = ide
      this.$scope = $scope
      this.reset()

      this.$scope.toggleHistory = () => {
        if (this.$scope.ui.view === 'history') {
          return this.hide()
        } else {
          return this.show()
        }
      }

      this.$scope.$watch('history.selection.updates', updates => {
        if (updates != null && updates.length > 0) {
          this._selectDocFromUpdates()
          return this.reloadDiff()
        }
      })

      this.$scope.$on('entity:selected', (event, entity) => {
        if (this.$scope.ui.view === 'history' && entity.type === 'doc') {
          this.$scope.history.selection.doc = entity
          return this.reloadDiff()
        }
      })
    }

    show() {
      this.$scope.ui.view = 'history'
      return this.reset()
    }

    hide() {
      this.$scope.ui.view = 'editor'
      // Make sure we run the 'open' logic for whatever is currently selected
      return this.$scope.$emit(
        'entity:selected',
        this.ide.fileTreeManager.findSelectedEntity()
      )
    }

    reset() {
      return (this.$scope.history = {
        updates: [],
        nextBeforeTimestamp: null,
        atEnd: false,
        selection: {
          updates: [],
          doc: null,
          range: {
            fromV: null,
            toV: null,
            start_ts: null,
            end_ts: null,
          },
        },
        diff: null,
      })
    }

    autoSelectRecentUpdates() {
      if (this.$scope.history.updates.length === 0) {
        return
      }

      this.$scope.history.updates[0].selectedTo = true

      let indexOfLastUpdateNotByMe = 0
      for (let i = 0; i < this.$scope.history.updates.length; i++) {
        const update = this.$scope.history.updates[i]
        if (this._updateContainsUserId(update, this.$scope.user.id)) {
          break
        }
        indexOfLastUpdateNotByMe = i
      }

      return (this.$scope.history.updates[
        indexOfLastUpdateNotByMe
      ].selectedFrom = true)
    }

    fetchNextBatchOfUpdates() {
      let url = `/project/${this.ide.project_id}/updates?min_count=${this.BATCH_SIZE}`
      if (this.$scope.history.nextBeforeTimestamp != null) {
        url += `&before=${this.$scope.history.nextBeforeTimestamp}`
      }
      this.$scope.history.loading = true
      return this.ide.$http.get(url).then(response => {
        const { data } = response
        this._loadUpdates(data.updates)
        this.$scope.history.nextBeforeTimestamp = data.nextBeforeTimestamp
        if (data.nextBeforeTimestamp == null) {
          this.$scope.history.atEnd = true
        }
        return (this.$scope.history.loading = false)
      })
    }

    reloadDiff() {
      let { diff } = this.$scope.history
      const { updates, doc } = this.$scope.history.selection
      const { fromV, toV, start_ts, end_ts } =
        this._calculateRangeFromSelection()

      if (doc == null) {
        return
      }

      if (
        diff != null &&
        diff.doc === doc &&
        diff.fromV === fromV &&
        diff.toV === toV
      ) {
        return
      }

      this.$scope.history.diff = diff = {
        fromV,
        toV,
        start_ts,
        end_ts,
        doc,
        error: false,
        pathname: doc.name,
      }

      if (!doc.deleted) {
        diff.loading = true
        let url = `/project/${this.$scope.project_id}/doc/${diff.doc.id}/diff`
        if (diff.fromV != null && diff.toV != null) {
          url += `?from=${diff.fromV}&to=${diff.toV}`
        }

        return this.ide.$http
          .get(url)
          .then(response => {
            const { data } = response
            diff.loading = false
            const { text, highlights } = this._parseDiff(data)
            diff.text = text
            return (diff.highlights = highlights)
          })
          .catch(function () {
            diff.loading = false
            return (diff.error = true)
          })
      } else {
        diff.deleted = true
        diff.restoreInProgress = false
        diff.restoreDeletedSuccess = false
        return (diff.restoredDocNewId = null)
      }
    }

    restoreDeletedDoc(doc) {
      const url = `/project/${this.$scope.project_id}/doc/${doc.id}/restore`
      return this.ide.$http.post(url, {
        name: doc.name,
        _csrf: window.csrfToken,
      })
    }

    restoreDiff(diff) {
      const url = `/project/${this.$scope.project_id}/doc/${diff.doc.id}/version/${diff.fromV}/restore`
      return this.ide.$http.post(url, { _csrf: window.csrfToken })
    }

    _parseDiff(diff) {
      let row = 0
      let column = 0
      const highlights = []
      let text = ''
      const iterable = diff.diff || []
      for (let i = 0; i < iterable.length; i++) {
        let endColumn, endRow
        const entry = iterable[i]
        let content = entry.u || entry.i || entry.d
        if (!content) {
          content = ''
        }
        text += content
        const lines = content.split('\n')
        const startRow = row
        const startColumn = column
        if (lines.length > 1) {
          endRow = startRow + lines.length - 1
          endColumn = lines[lines.length - 1].length
        } else {
          endRow = startRow
          endColumn = startColumn + lines[0].length
        }
        row = endRow
        column = endColumn

        const range = {
          start: {
            row: startRow,
            column: startColumn,
          },
          end: {
            row: endRow,
            column: endColumn,
          },
        }

        if (entry.i != null || entry.d != null) {
          const name = displayNameForUser(entry.meta.user)
          const date = moment(entry.meta.end_ts).format('Do MMM YYYY, h:mm a')
          if (entry.i != null) {
            highlights.push({
              label: `Added by ${name} on ${date}`,
              highlight: range,
              hue: ColorManager.getHueForUserId(
                entry.meta.user != null ? entry.meta.user.id : undefined
              ),
            })
          } else if (entry.d != null) {
            highlights.push({
              label: `Deleted by ${name} on ${date}`,
              strikeThrough: range,
              hue: ColorManager.getHueForUserId(
                entry.meta.user != null ? entry.meta.user.id : undefined
              ),
            })
          }
        }
      }

      return { text, highlights }
    }

    _loadUpdates(updates) {
      if (updates == null) {
        updates = []
      }
      let previousUpdate =
        this.$scope.history.updates[this.$scope.history.updates.length - 1]

      for (const update of Array.from(updates)) {
        update.pathnames = [] // Used for display
        const object = update.docs || {}
        for (const doc_id in object) {
          const doc = object[doc_id]
          doc.entity = this.ide.fileTreeManager.findEntityById(doc_id, {
            includeDeleted: true,
          })
          update.pathnames.push(doc.entity.name)
        }

        for (const user of Array.from(update.meta.users || [])) {
          if (user != null) {
            user.hue = ColorManager.getHueForUserId(user.id)
          }
        }

        if (
          previousUpdate == null ||
          !moment(previousUpdate.meta.end_ts).isSame(update.meta.end_ts, 'day')
        ) {
          update.meta.first_in_day = true
        }

        update.selectedFrom = false
        update.selectedTo = false
        update.inSelection = false

        previousUpdate = update
      }

      const firstLoad = this.$scope.history.updates.length === 0

      this.$scope.history.updates = this.$scope.history.updates.concat(updates)

      if (firstLoad) {
        return this.autoSelectRecentUpdates()
      }
    }

    _calculateRangeFromSelection() {
      let end_ts, start_ts, toV
      let fromV = (toV = start_ts = end_ts = null)

      const selected_doc_id =
        this.$scope.history.selection.doc != null
          ? this.$scope.history.selection.doc.id
          : undefined

      for (const update of Array.from(
        this.$scope.history.selection.updates || []
      )) {
        for (const doc_id in update.docs) {
          const doc = update.docs[doc_id]
          if (doc_id === selected_doc_id) {
            if (fromV != null && toV != null) {
              fromV = Math.min(fromV, doc.fromV)
              toV = Math.max(toV, doc.toV)
              start_ts = Math.min(start_ts, update.meta.start_ts)
              end_ts = Math.max(end_ts, update.meta.end_ts)
            } else {
              ;({ fromV } = doc)
              ;({ toV } = doc)
              ;({ start_ts } = update.meta)
              ;({ end_ts } = update.meta)
            }
            break
          }
        }
      }

      return { fromV, toV, start_ts, end_ts }
    }

    // Set the track changes selected doc to one of the docs in the range
    // of currently selected updates. If we already have a selected doc
    // then prefer this one if present.
    _selectDocFromUpdates() {
      let doc, doc_id
      const affected_docs = {}
      for (const update of Array.from(this.$scope.history.selection.updates)) {
        for (doc_id in update.docs) {
          doc = update.docs[doc_id]
          affected_docs[doc_id] = doc.entity
        }
      }

      let selected_doc = this.$scope.history.selection.doc
      if (selected_doc != null && affected_docs[selected_doc.id] != null) {
        // Selected doc is already open
      } else {
        const doc_ids = Object.keys(affected_docs)
        if (doc_ids.length > 0) {
          const doc_id = doc_ids[0]
          doc = affected_docs[doc_id]
          selected_doc = doc
        }
      }

      this.$scope.history.selection.doc = selected_doc
      return this.ide.fileTreeManager.selectEntity(selected_doc)
    }

    _updateContainsUserId(update, user_id) {
      for (const user of Array.from(update.meta.users)) {
        if ((user != null ? user.id : undefined) === user_id) {
          return true
        }
      }
      return false
    }
  }
  HistoryManager.initClass()
  return HistoryManager
})()
