// ------------------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------------------

/**
 * Capitalize a string.
 * @param {string} str
 */
function capitalize(str) {
  return str.charAt(0).toUpperCase() + str.slice(1)
}
/**
 * Checks whether the given string has symbols.
 * @param {string} str
 */
function hasSymbols(str) {
  return /[!"#%&'()*+,./:;<=>?@[\\\]^`{|}]/u.exec(str) // without " ", "$", "-" and "_"
}
/**
 * Checks whether the given string has upper.
 * @param {string} str
 */
function hasUpper(str) {
  return /[A-Z]/u.exec(str)
}

/**
 * Convert text to kebab-case
 * @param {string} str Text to be converted
 * @return {string}
 */
function kebabCase(str) {
  return str
    .replace(/_/gu, '-')
    .replace(/\B([A-Z])/gu, '-$1')
    .toLowerCase()
}

/**
 * Checks whether the given string is kebab-case.
 * @param {string} str
 */
function isKebabCase(str) {
  if (
    hasUpper(str) ||
    hasSymbols(str) ||
    /^-/u.exec(str) || // starts with hyphen is not kebab-case
    /_|--|\s/u.exec(str)
  ) {
    return false
  }
  return true
}

/**
 * Convert text to snake_case
 * @param {string} str Text to be converted
 * @return {string}
 */
function snakeCase(str) {
  return str
    .replace(/\B([A-Z])/gu, '_$1')
    .replace(/-/gu, '_')
    .toLowerCase()
}

/**
 * Checks whether the given string is snake_case.
 * @param {string} str
 */
function isSnakeCase(str) {
  if (hasUpper(str) || hasSymbols(str) || /-|__|\s/u.exec(str)) {
    return false
  }
  return true
}

/**
 * Convert text to camelCase
 * @param {string} str Text to be converted
 * @return {string} Converted string
 */
function camelCase(str) {
  if (isPascalCase(str)) {
    return str.charAt(0).toLowerCase() + str.slice(1)
  }
  return str.replace(/[-_](\w)/gu, (_, c) => (c ? c.toUpperCase() : ''))
}

/**
 * Checks whether the given string is camelCase.
 * @param {string} str
 */
function isCamelCase(str) {
  if (
    hasSymbols(str) ||
    /^[A-Z]/u.exec(str) ||
    /-|_|\s/u.exec(str) // kebab or snake or space
  ) {
    return false
  }
  return true
}

/**
 * Convert text to PascalCase
 * @param {string} str Text to be converted
 * @return {string} Converted string
 */
function pascalCase(str) {
  return capitalize(camelCase(str))
}

/**
 * Checks whether the given string is PascalCase.
 * @param {string} str
 */
function isPascalCase(str) {
  if (
    hasSymbols(str) ||
    /^[a-z]/u.exec(str) ||
    /-|_|\s/u.exec(str) // kebab or snake or space
  ) {
    return false
  }
  return true
}

const convertersMap = {
  'kebab-case': kebabCase,
  snake_case: snakeCase,
  camelCase,
  PascalCase: pascalCase
}

const checkersMap = {
  'kebab-case': isKebabCase,
  snake_case: isSnakeCase,
  camelCase: isCamelCase,
  PascalCase: isPascalCase
}
/**
 * Return case checker
 * @param { 'camelCase' | 'kebab-case' | 'PascalCase' | 'snake_case' } name type of checker to return ('camelCase', 'kebab-case', 'PascalCase')
 * @return {isKebabCase|isCamelCase|isPascalCase|isSnakeCase}
 */
function getChecker(name) {
  return checkersMap[name] || isPascalCase
}

/**
 * Return case converter
 * @param { 'camelCase' | 'kebab-case' | 'PascalCase' | 'snake_case' } name type of converter to return ('camelCase', 'kebab-case', 'PascalCase')
 * @return {kebabCase|camelCase|pascalCase|snakeCase}
 */
function getConverter(name) {
  return convertersMap[name] || pascalCase
}

module.exports = {
  allowedCaseOptions: ['camelCase', 'kebab-case', 'PascalCase'],

  /**
   * Return case converter
   * @param {string} name type of converter to return ('camelCase', 'kebab-case', 'PascalCase')
   * @return {kebabCase|camelCase|pascalCase|snakeCase}
   */
  getConverter,

  /**
   * Return case checker
   * @param {string} name type of checker to return ('camelCase', 'kebab-case', 'PascalCase')
   * @return {isKebabCase|isCamelCase|isPascalCase|isSnakeCase}
   */
  getChecker,

  /**
   * Return case exact converter.
   * If the converted result is not the correct case, the original value is returned.
   * @param { 'camelCase' | 'kebab-case' | 'PascalCase' | 'snake_case' } name type of converter to return ('camelCase', 'kebab-case', 'PascalCase')
   * @return {kebabCase|camelCase|pascalCase|snakeCase}
   */
  getExactConverter(name) {
    const converter = getConverter(name)
    const checker = getChecker(name)
    return (str) => {
      const result = converter(str)
      return checker(result) ? result : str /* cannot convert */
    }
  },

  camelCase,
  pascalCase,
  kebabCase,
  snakeCase,

  isCamelCase,
  isPascalCase,
  isKebabCase,
  isSnakeCase,

  capitalize
}
