/* eslint-disable
    handle-callback-err,
    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
 * DS103: Rewrite code to no longer use __guard__
 * DS201: Simplify complex destructure assignments
 * DS207: Consider shorter variations of null checks
 * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
 */
let ResourceStateManager
const Path = require('path')
const fs = require('fs')
const logger = require('logger-sharelatex')
const settings = require('settings-sharelatex')
const Errors = require('./Errors')
const SafeReader = require('./SafeReader')

module.exports = ResourceStateManager = {
  // The sync state is an identifier which must match for an
  // incremental update to be allowed.
  //
  // The initial value is passed in and stored on a full
  // compile, along with the list of resources..
  //
  // Subsequent incremental compiles must come with the same value - if
  // not they will be rejected with a 409 Conflict response. The
  // previous list of resources is returned.
  //
  // An incremental compile can only update existing files with new
  // content.  The sync state identifier must change if any docs or
  // files are moved, added, deleted or renamed.

  SYNC_STATE_FILE: '.project-sync-state',
  SYNC_STATE_MAX_SIZE: 128 * 1024,

  saveProjectState(state, resources, basePath, callback) {
    if (callback == null) {
      callback = function(error) {}
    }
    const stateFile = Path.join(basePath, this.SYNC_STATE_FILE)
    if (state == null) {
      // remove the file if no state passed in
      logger.log({ state, basePath }, 'clearing sync state')
      return fs.unlink(stateFile, function(err) {
        if (err != null && err.code !== 'ENOENT') {
          return callback(err)
        } else {
          return callback()
        }
      })
    } else {
      logger.log({ state, basePath }, 'writing sync state')
      const resourceList = Array.from(resources).map(resource => resource.path)
      return fs.writeFile(
        stateFile,
        [...Array.from(resourceList), `stateHash:${state}`].join('\n'),
        callback
      )
    }
  },

  checkProjectStateMatches(state, basePath, callback) {
    if (callback == null) {
      callback = function(error, resources) {}
    }
    const stateFile = Path.join(basePath, this.SYNC_STATE_FILE)
    const size = this.SYNC_STATE_MAX_SIZE
    return SafeReader.readFile(stateFile, size, 'utf8', function(
      err,
      result,
      bytesRead
    ) {
      if (err != null) {
        return callback(err)
      }
      if (bytesRead === size) {
        logger.error(
          { file: stateFile, size, bytesRead },
          'project state file truncated'
        )
      }
      const array =
        __guard__(result != null ? result.toString() : undefined, x =>
          x.split('\n')
        ) || []
      const adjustedLength = Math.max(array.length, 1)
      const resourceList = array.slice(0, adjustedLength - 1)
      const oldState = array[adjustedLength - 1]
      const newState = `stateHash:${state}`
      logger.log(
        { state, oldState, basePath, stateMatches: newState === oldState },
        'checking sync state'
      )
      if (newState !== oldState) {
        return callback(
          new Errors.FilesOutOfSyncError('invalid state for incremental update')
        )
      } else {
        const resources = Array.from(resourceList).map(path => ({ path }))
        return callback(null, resources)
      }
    })
  },

  checkResourceFiles(resources, allFiles, basePath, callback) {
    // check the paths are all relative to current directory
    let file
    if (callback == null) {
      callback = function(error) {}
    }
    for (file of Array.from(resources || [])) {
      for (const dir of Array.from(
        __guard__(file != null ? file.path : undefined, x => x.split('/'))
      )) {
        if (dir === '..') {
          return callback(new Error('relative path in resource file list'))
        }
      }
    }
    // check if any of the input files are not present in list of files
    const seenFile = {}
    for (file of Array.from(allFiles)) {
      seenFile[file] = true
    }
    const missingFiles = Array.from(resources)
      .filter(resource => !seenFile[resource.path])
      .map(resource => resource.path)
    if ((missingFiles != null ? missingFiles.length : undefined) > 0) {
      logger.err(
        { missingFiles, basePath, allFiles, resources },
        'missing input files for project'
      )
      return callback(
        new Errors.FilesOutOfSyncError(
          'resource files missing in incremental update'
        )
      )
    } else {
      return callback()
    }
  }
}

function __guard__(value, transform) {
  return typeof value !== 'undefined' && value !== null
    ? transform(value)
    : undefined
}
