import mongodb from 'mongodb-legacy'

import _ from 'lodash'
import Settings from '@overleaf/settings'
import OError from '@overleaf/o-error'
import SplitTestHandler from '../SplitTests/SplitTestHandler.mjs'

const { ObjectId } = mongodb

/**
 * @import { MongoProject } from "./types"
 */

const ENGINE_TO_COMPILER_MAP = {
  latex_dvipdf: 'latex',
  pdflatex: 'pdflatex',
  xelatex: 'xelatex',
  lualatex: 'lualatex',
}

export default {
  compilerFromV1Engine,
  isArchived,
  isTrashed,
  isArchivedOrTrashed,
  getAllowedImagesForUser,
  ensureNameIsUnique,
}

function compilerFromV1Engine(engine) {
  return ENGINE_TO_COMPILER_MAP[engine]
}

/**
 @param {MongoProject} project
 @param {string} rawUserId
 * @returns {boolean}
 */
function isArchived(project, rawUserId) {
  const userId = new ObjectId(rawUserId)

  return (project.archived || []).some(id => id.equals(userId))
}

/**
 * @param {MongoProject} project
 * @param {string} rawUserId
 * @returns {boolean}
 */
function isTrashed(project, rawUserId) {
  const userId = new ObjectId(rawUserId)

  return (project.trashed || []).some(id => id.equals(userId))
}

/**
 * @param {MongoProject} project
 * @param {string} userId
 * @returns {boolean}
 */
function isArchivedOrTrashed(project, userId) {
  return isArchived(project, userId) || isTrashed(project, userId)
}

/**
 * @param {string[]} nameList
 * @param {string} name
 * @param {string[]} suffixes
 * @param {number} maxLength
 * @returns string
 */
function ensureNameIsUnique(nameList, name, suffixes, maxLength) {
  // create a set of all project names
  if (suffixes == null) {
    suffixes = []
  }
  const allNames = new Set(nameList)
  const isUnique = x => !allNames.has(x)
  // check if the supplied name is already unique
  if (isUnique(name)) {
    return name
  }
  // the name already exists, try adding the user-supplied suffixes to generate a unique name
  for (const suffix of suffixes) {
    const candidateName = _addSuffixToProjectName(name, suffix, maxLength)
    if (isUnique(candidateName)) {
      return candidateName
    }
  }
  // if there are no (more) suffixes, use a numeric one
  const uniqueName = _addNumericSuffixToProjectName(name, allNames, maxLength)
  if (uniqueName != null) {
    return uniqueName
  } else {
    throw new OError('Failed to generate a unique name', { name })
  }
}

function _addSuffixToProjectName(name, suffix, maxLength) {
  // append the suffix and truncate the project title if needed
  if (suffix == null) {
    suffix = ''
  }
  const truncatedLength = maxLength - suffix.length
  return name.substr(0, truncatedLength) + suffix
}

/**
 * @param {string} name
 * @param {Set<string>} allProjectNames
 * @param {number} maxLength
 */
function _addNumericSuffixToProjectName(name, allProjectNames, maxLength) {
  const NUMERIC_SUFFIX_MATCH = / \((\d+)\)$/
  const suffixedName = function (basename, number) {
    const suffix = ` (${number})`
    return basename.substr(0, maxLength - suffix.length) + suffix
  }

  const match = name.match(NUMERIC_SUFFIX_MATCH)
  let basename = name
  let n = 1

  if (match != null) {
    basename = name.replace(NUMERIC_SUFFIX_MATCH, '')
    n = parseInt(match[1])
  }

  const prefixMatcher = new RegExp(`^${_.escapeRegExp(basename)} \\(\\d+\\)$`)

  const projectNamesWithSamePrefix = Array.from(allProjectNames).filter(name =>
    prefixMatcher.test(name)
  )
  const last = allProjectNames.size + n
  const nIsLikelyAYear = n > 1000 && projectNamesWithSamePrefix.length < n / 2
  if (nIsLikelyAYear) {
    basename = name
    n = 1
  }

  while (n <= last) {
    const candidate = suffixedName(basename, n)
    if (!allProjectNames.has(candidate)) {
      return candidate
    }
    n += 1
  }

  return null
}

async function _monthlyExperimentalImageAllowed(user) {
  const userId = user?._id?.toString()
  if (!userId) return false
  const { variant } = await SplitTestHandler.promises.getAssignmentForUser(
    userId,
    'monthly-texlive'
  )
  return variant === 'enabled'
}

function _imageAllowed(
  image,
  alphaImagesAllowed,
  monthlyExperimentalImagesAllowed
) {
  if (image.alphaOnly && !alphaImagesAllowed) {
    return false
  }
  if (image.monthlyExperimental && !monthlyExperimentalImagesAllowed) {
    return false
  }
  return true
}

async function getAllowedImagesForUser(user) {
  let images = Settings.allowedImageNames || []

  const alphaImagesAllowed = Boolean(user?.alphaProgram)
  const monthlyExperimentalImagesAllowed =
    await _monthlyExperimentalImageAllowed(user)

  images = images.map(image => {
    return {
      ...image,
      allowed: _imageAllowed(
        image,
        alphaImagesAllowed,
        monthlyExperimentalImagesAllowed
      ),
      rolling: image.monthlyExperimental,
    }
  })

  return images
}
